{- 2017.02.21-22 《Haskell趣学指南 —— Learning You a Haskell for Great Good!》 学习了Haskell的基本语法,并实现了一些模块和程序,学习的时候,应该注意GHCI模式和文本模式时的一些语法的差别 [官网](http://learnyouahaskell.com) shell > ghc xxx.hs # 编译文件 shell > runhaskell xxx.hs # 直接运行hs文件 shell > ghci # 进入Haskell解释器 ghci > :l test.hs -- 载入test.hs文件 ghci > :r -- 从新载入文件 注意 1. ghci中定义变量须要使用let关键字,而hs文件中不须要 2. ghci中,冒号(:)开头标示命令,相似于vi。如 :? 表示查找助,:l 表示加载文件。 -} ---------------------------------------------------------------------------------------- -- ch00 HelloHaskell import System.IO main = do putStr $ "Hello," -- Hello, putStrLn $ "Haskell" -- Haskell\n print $ "Hello,Haskell" -- "Hello,Haskell" ---------------------------------------------------------------------------------------- -- ch01 Introduction doubleMe x = x + x -- 前缀函数: doubleMe 3 doubleUs x y = x*2 + y*2 -- 中缀函数: doubleUs 3 4 或 3 `doubleUs` 4 -- Haskell中程序是一系列的函数的集合,函数取数据做为参数,并将它们转为想要的结果。 -- 每一个函数都会返回一个结果,能够做为其余函数的子结构。 -- Hasekill中if返回的是表达式(expression)不是语句(statement)。 operatedIfSmall x = (if x>=50 then x else x*2) + 1 -- 1.3 introduction to list -- 列表list是一种单类型的数据结构,用来存储同构元素 let vec = [1] ++ 2:[3,4,5] ++ 6:[7,8,9] ++ [10] --语法糖 print vec -- [1,2,3,4,5,6,7,8,9,10] let mat = [[1,2,3],[4,5,6],[7,8,9]] --嵌套列表 mat!!2!!2 -- 使用双感叹号来索引列表中的元素,注意从0开始 let vec = [1,2,3,4,5] vec!!0 -- 1,访问列表中的元素 vec!!2 -- 3 head vec -- 1 tail vec -- [2,3,4,5] init vec -- [1,2,3,4] last vec -- 5 length vec -- 5 null vec -- False null [] -- True reserve vec -- [5,4,3,2,1] take 3 vec -- [1,2,3] drop 3 vec -- [4,5] maximum vec -- 5 minimum vec -- 1 sum vec -- 15 product vec -- 120 3 `elem` vec -- True 13 `elem` vec -- False -- 生成列表区间 print [1..10] -- [1,2,3,4,5,6,7,8,9,10] print [2,4..10] -- [2,4,6,8,10] -- 生成无限列表 take 10 (cycle [1,2,3,4]) take 10 (repeat 5) replicate 10 5 -- 列表推导 list compression print [x*2 | x<- [1..10]] --[2,4,6,8,10,12,14,16,18,20] -- 增长谓词(predicate),过滤(filter) print [x*2 | x<- [1..10], x*2>10] --[12,14,16,18,20] -- 判断一个数是否为奇数,奇数返回True,偶数返回False。 let boomBangs xs = [ if x < 6 then "BOOM!" else "BANG!" | x <- xs, odd x] boomBangs [1..10] -- 对两个列表组合,使用逗号隔开的多个谓词 print [x*y| x<-[3,4,5],y<-[8,9,10]] -- [24,27,30,32,36,40,40,45,50] print [x*y| x<-[3,4,5],y<-[8,9,10],x*y>28, x* y<45] -- [30,32,36,40,40] -- 使用一组名词和一组形容词构成列表推导式子 let nouns = ["hobo","frog","pope"] let adjectives = ["lazy","grouchy","scheming"] [adjective ++ " " ++ noun | adjective <- adjectives, noun <- nouns] -- ["lazy hobo","lazy frog","lazy pope","grouchy hobo","grouchy frog", -- "grouchy pope","scheming hobo","scheming frog","scheming pope"] -- 自定义length函数,下划线(_)表示可有可无的临时变量 let mylength xs = sum [1| _ <- xs] mylength [2,4..10] -- 去除字符串中非大写字母 let removeNonUppercase xs = [c |c<-xs, c `elem` ['A'..'Z']] removeNonUppercase "Hello,World!" -- "HW" -- 嵌套处理嵌套列表(列表的列表) let xxs = [[1,2,3,4],[5,6,7,8]] let myevens xxs = [[x|x<-xs, even x] | xs <- xxs] myevens xxs -- 元组tuple,又称有序对pair,容许存储多个异构的值 let tt =(1,2.0,'3',"four") -- 使用元组的列表存储坐标 let pts = [(x,y)|x<-[1..5],y<-[5,4..1]] pts!!3 -- (4,1) fst (pts!!3) -- 4 snd (pts!!3) -- 1 -- zip交叉配对 let tbs = zip [1..] ["one","two","three","four","five"] print tbs -- 查找边长是整数且周长为24的直角三角形 let rightTriangle24 = [(a,b,c)|c<-[1..(24 `div` 2)],b<-[1..c],a<-[1..b],a^2+b^2==c^2, a+b+c==24] rightTriangle24 ---------------------------------------------------------------------------------------- -- ch02 type reference {- Haskell常见类型有 Bool,Int,Float,Double,Num,Char,[],()等,凡是类型,首字母必须大写。 使用小写字母表示的类型变量,表示能够是任意类型。 :type True -- True :: Bool :type fst -- fst :: (a, b) -> a 类型类定义行为的抽象接口(相似于纯虚基类),若是某个类型是某个类型类的实例(instance),那么它必须实现该类型所描述的行为。 具体来讲,类型类就是一组函数集合,若是某类型实现某类型类的实例,就须要为这一类类型提供这些函数的实现。 :type (==) -- (==) :: (Eq a) => a -> a -> Bool 常见类型类有Eq等性测试、Ord比较大小、Show转化为字符串、Read从字符串读取参数并转为为种类型、Enum连续枚举、Bounded上下界限、Num数值类、Floating浮点类、Integeral实数和整数。一个类型能够有多个类型类实例,一个类型类能够包含多个类型做为实例。有时候某些类型是另外类型的先决条件(prerequisite),如Eq是Ord实例的先决条件。 Eq:必须定义了==和/=两个函数 Ord:包含了全部标准类型的比较函数,如<,>,<=,>=等。compare函数取两个Ord中的相同类型的值做为参数,返回一个Ordering类型的值(有GT、LT、EQ三种值)。 Show:常见的是show函数 Read:常见的是read函数 Enum:连续枚举,每一个值都有前驱predecessor和后继successer,能够分别经过pred和succ函数获得。 Bounded:反应的是实例类型的上下限,经过maxBound和minBound得到,这两个函数类型都是多态函数,原型为 (Bounded a)=>a。 -} let tt =(True,1,2.0,'3',"four") :type tt -- tt :: (Fractional t1, Num t) => (Bool, t, t1, Char, [Char]) let tf = (head,tail,init,last) :type tf -- tf :: ([a] -> a, [a1] -> [a1], [a2] -> [a2], [a3] -> a3) :t (==) -- (==) :: Eq a => a -> a -> Bool 5 == 5 -- True 5 /= 5 -- False :t (>) -- (>) :: Ord a => a -> a -> Bool 5 > 5 -- False 5 `compare` 5 -- EQ :t show -- show :: Show a => a -> String show 3.14 -- "3.14" :t read -- read :: Read a => String -> a read "True" || False -- True [read "True",False] -- [True, False] (read "3.14"::Double) + 2.4 -- 5.54 read "[1,2,3,4,5]" ++ [6] -- [1,2,3,4,5,6] (read "[1,2,3,4,5]"::[Int]) ++[6] -- [1,2,3,4,5,6] [1..5] --[1,2,3,4,5] [LT ..GT] --[LT,EQ,GT] succ 'D' -- 'E' pred 'D' -- 'C' minBound::(Bool,Int,Char) -- (False,-9223372036854775808,'\NUL') maxBound::(Bool,Int,Char) -- (True,9223372036854775807,'\1114111') :t fromIntegral -- fromIntegral :: (Integral a, Num b) => a -> b length [x|x<-[1..100],x `mod` 2 == 0, x `mod` 3 == 0] --16 fromIntegral (length [x|x<-[1..100],x `mod` 2 == 0, x `mod` 3 == 0]) + 3.14 --19.14 -- ch03 函数语法 -- 模式匹配(pattern matching)是经过检查数据是否符合特定的结构来判断是否匹配,并从模式中解析数据。 -- 在模式中给出小写字母的名字而不是具体的值,那么就是一个万能模式(catchal pattern),它总能匹配输入参数,并将其绑定到模式中的名字供咱们引用。 -- 模式用来检查参数的结构是否匹配,而哨兵(guard)用来检查参数的性质时候威震,相似于if条件判断语句。 -- 万能匹配模式,替代if-then-else决策树 -- 辨别1-4并输出相应的单词,不然令作处理 sayMe :: Int -> String sayMe 1 = "One" sayMe 2 = "Two" sayMe 3 = "Three" sayMe 4 = "Four" sayMe x = "Not between 1 and 4" -- print [sayMe x| x<- [1..5]] -- ["One","Two","Three","Four","Not between 1 and 4"] -- 阶乘函数 product[1..n] factorial :: Int -> Int factorial 0 = 1 factorial n = factorial(n-1) * n -- factorial 10 -- 3628800 -- 元组的模式匹配 addTuples :: (Double,Double) -> (Double,Double) -> (Double,Double) addTuples (x1,y1) (x2,y2) = (x1+x2,y1+y2) -- addTuples (1,2) (3,4) -- (4.0,6.0) -- 获取三元组第三个元素 thirdItem :: (a,b,c) -> c thirdItem (_,_,z) = z -- thirdItem (1,2,3) -- 3 -- 列表推导模式匹配,使用运行时错误来指明错误 myfirst :: [a] -> a myfirst [] = error "Cannot call myfirst on an empty list, dummy!" myfirst (x:_) = x -- 列表匹配,绑定多个变量 tell :: (Show a) => [a] -> String tell [] = "The list is empty" tell (x:[]) = "The list has one element: " ++ show x tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y tell (x:y:_) = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y -- tell [1..5] -- "This list is long. The first two elements are: 1 and 2" -- 使用as模式,将模式的值分割为多个项的同时保持对总体的引用。 firstLetter :: String -> String firstLetter "" = "Empty string, whoops!" firstLetter all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x] -- firstLetter "Hello" -- "The first letter of Hello is H" -- 使用哨兵,哨兵是一个布尔表达式,跟在竖线(|)右边,计算为真的时候就选择对应的函数体,不然向后查找。 tellBMI :: (RealFloat a) => a -> String tellBMI bmi | bmi <= 18.5 = "You're underweight, you emo, you!" | bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!" | bmi <= 30.0 = "You're fat! Lose some weight, fatty!" | otherwise = "You're a whale, congratulations!" -- tellBMI 21 -- 关于计算时数值的一些问题,参考https://hackage.haskell.org/package/base-4.9.1.0/docs/Numeric.html calcBMI :: (RealFloat a, Show a) => a -> a -> String calcBMI weight height | bmi <= skinny = info++ "You're underweight, you emo, you!" | bmi <= normal = info++ "You're supposedly normal. Pffft, I bet you're ugly!" | bmi <= fat = info++ "You're fat! Lose some weight, fatty!" | otherwise = info++ "You're a whale, congratulations!" where bmi = weight / height **2.5 *1.3 (skinny,normal,fat) = (18.5, 25.0, 30.0) -- man24,woman22 info= "You are " ++ show weight ++" kgs, " ++show height ++ " m, and your bmi is " ++ show bmi ++ "! \n" -- tellBMI 63 172 --列表生成器generator,let语句,谓词等构造列表推导式 calcBMI2 :: [(Double,Double)] ->[Double] calcBMI2 xs = [bmi|(w,h)<-xs, let bmi = w/h**2.5 *1.3,bmi>15.0] -- 使用case语句,其实模式匹配不过是case表达式的语法糖而已 describeList :: [a] -> String describeList xs = "The list is " ++ case xs of [] -> "empty." [x] -> "a singleton list." xs -> "a longer list." -- 使用where what语句替换case语句 describeList2 :: [a] -> String describeList2 xs = "The list is " ++ what xs where what [] = "empty." what [x] = "a singleton list." what xs = "a longer list." ---------------------------------------------------------------------------------------- -- ch4 递归 -- 按照递归的基本思想,递归倾向于将问题展开为一样的子问题,并不断对子问题进行展开、求解,直达到达问题的基准条件(base case)为止。基准条件中问题没必要再进行分解,必须有程序员知名一个非递归结果。 -- 编写函数的时候,首先肯定基准条件,也就是对应特殊输入的简单非递归解(好比空列表排序结果仍然为空);而后将问题分解为一个或者多个子问题,并递归地调用自身,最后基于子问题里获得的结果,组合成为最终的解(好比快速排序中,将列表分解为两个列表和一个主元,并对这两个列表分别应用同一个函数进行排序,最后获得的结果从新组合起来,就是最终排序后的列表了。 -- 递归在Haskell中很重要,命令式语言中倾向于“告知如何计算”(how),而Haskell倾向于“声明问题是什么”(what)。这也就是说Haskell编程是,重要的不是给出解题步骤,而是定义问题和解的描述。此时递归将大显身手,一旦肯定了正确的基准条件和正确的子问题,Haskell就会根据子问题的求解结果组合成最终的结果。 -- 递归计算最大值,注意何时用方括号,何时用圆括号 mymax :: (Ord a) => [a] -> a mymax [] = error "empty list" -- 基准模式1,空列表抛出错误 mymax [x] = x -- 基准模式2,单元数列表返回元素自己 mymax (x:xs) = max x (mymax xs) -- 模式3,列表形式,递归调用自己 -- mymax [1,2,5,3,4] --5 -- 复制n份,取一个整数和一个元素,返回一个包含该整数个重复元素的列表。 -- 由于是布尔判断,因此使用了哨兵而不是模式匹配。 myreplicate :: Int -> a -> [a] myreplicate n x | n<=0 = [] | otherwise = x:myreplicate (n-1) x -- myreplicate 4 3 -- [3,3,3,3] -- 从列表中选取前n个元素 mytake :: (Num i, Ord i) => i -> [a] ->[a] mytake n _ | n<=0 = [] -- 基准模式1,若是要获取不大于0个元素,则返回空列表;注意这里的哨兵没有otherwise,能够转到其余的模式。 mytake _ [] = [] -- 基准模式2,若是列表为空,返回空列表 mytake n (x:xs) = x: mytake (n-1) xs -- 迭代模式,将列表切片,并递归操做 -- mytake 3 [1..5] -- [1,2,3] -- 翻转列表 myreverse :: [a] -> [a] myreverse [] = [] myreverse (x:xs) = myreverse xs ++ [x] -- myreverse [1..5] -- [5,4,3,2,1] -- 生成无限列表 myrepeat :: a -> [a] myrepeat x = [x] ++ myrepeat x -- mytake 4 (myrepeat 3) -- [3,3,3,3] -- 打包 myzip :: [a]->[b]->[(a,b)] myzip [] _ = [] myzip _ [] = [] myzip (x:xs) (y:ys) = (x,y):myzip xs ys -- myzip [1..5] [5..1] -- [] -- myzip [1,2,3,4,5] [5,4,3,2,1] -- [(1,5),(2,4),(3,3),(4,2),(5,1)] -- 查看是否子元素 myelem :: (Eq a) => a -> [a] -> Bool myelem a [] = False myelem a (x:xs) | a == x = True | otherwise = a `myelem` xs -- 3 `myelem` [1..5] -- True -- 13 `myelem` [1..5] -- False -- 快速排序 -- 使用谓词和列表推导 myqsort :: (Ord a) => [a] -> [a] myqsort [] = [] -- 基准模式是空列表 myqsort (x:xs) = -- 迭代模式是,将小于当前元素的放在左边,大于当前元素的放在右边,分别对左右两部分进行快排 myqsort [a|a<-xs, a<=x] ++ [x] ++ myqsort [a|a<-xs, a>x] -- 使用filter函数 myqsort2 :: (Ord a) => [a] -> [a] myqsort2 [] = [] -- 基准模式是空列表 myqsort2 (x:xs) = -- 迭代模式是,将小于当前元素的放在左边,大于当前元素的放在右边,分别对左右两部分进行快排 let smallerOrEqual = filter (<=x) xs larger = filter (>x) xs in myqsort2 smallerOrEqual ++ [x] ++ myqsort2 larger -- myqsort [1,5,3,2,7,6,4,9,8] -- [1,2,3,4,5,6,7,8,9] ---------------------------------------------------------------------------------------- -- ch05 高阶函数 -- Haskell中能够把函数用作参数和返回值,这样的特定成为高阶函数。 -- C++14支持函数返回自动类型推导,能够返回一个函数内部的lambda,也就是支持高阶函数,好BUG啊。 {- // g++ -std=c++14 xxx.cpp auto fun(){ auto myfun = [](int a, int b){return a>b?a:b;}; return myfun; } auto myfun = fun(); cout << myfun(3,4)<<endl; -} -- 只要在类型签名中看到了箭头符号(->),就意味着它是一个将箭头左边视为参数类型并将箭头右侧部分做为返回类型的函数。 -- 若是遇到a->(a->a)这样的类型签名,说明它是一个函数,取类型为a的值做为参数,返回一个函数一样取类型a为参数,而且返回值类型是a。 -- 这样作,能够以部分参数调用函数,获得一个部分应用(Partial application)函数,该函数所接受的参数是一个函数,和以前少传入的参数类型一致。 multThree :: Int -> (Int -> (Int -> Int)) multThree x y z = x*y*z -- multiThree 3 4 5 -- 60 -- 使用部分应用 multTwoWithThree :: Int -> Int -> Int multTwoWithThree x y = multThree 3 x y -- multiTwoWithThree 4 5 -- 60 -- 在ghc中使用部分应用 multTwoWithThree2 = multThree 3 -- hs文件中不用let,可是ghc中须要使用let -- multiTwoWithThree2 4 5 -- 60 -- 对中缀函数使用截断,除以10 dividedByTen :: (Floating a) => a -> a dividedByTen = (/10) -- dividedByTen 200 -- 20.0 -- 对中缀函数使用截断 subtractFour :: Int -> Int subtractFour = (subtract 4) -- [subtractFour x | x<-[1..5]] -- [-3,-2,-1,0,1] -- 对中缀函数使用截断 isUpperCase :: Char -> Bool isUpperCase = (`elem` ['A'..'Z']) -- [isUpperCase x | x<-"Hello"] -- [True,False,False,False,False] -- 取另外一个函数做为参数,并返回函数 applyTwice :: (a->a) -> a -> a applyTwice f x = f (f x) -- applyTwice (+3) 10 -- 16 -- applyTwice (++[3]) [10] -- [10,3,3] -- applyTwice ([3]++) [10] -- [3,3,10] -- 实现zipWith,取一个函数和两个列表做为输入,使用函数调用从两个列表中取出的相应元素 -- 在编写高阶函数时,若是拿不许类型,能够先省略类型声明,而后在使用:t命令查看Haskell的类型推导结果 myzipWith :: (a -> b -> c) -> [a] -> [b] -> [c] myzipWith _ [] _ = [] myzipWith _ _ [] = [] myzipWith f (x:xs) (y:ys) = f x y : myzipWith f xs ys -- myzipWith (+) [1,2,3,4,5] [11,12,13,14,15] -- [12,14,16,18,20] -- myzipWith (*) (replicate 5 2) [1..] -- [2,4,6,8,10] -- 实现flip函数,以一个函数为参数,返回参数顺序颠倒的函数 myflip :: (a->b->c) -> (b -> a -> c) myflip f x y = f y x -- zip [1..5] "hello" -- [(1,'h'),(2,'e'),(3,'l'),(4,'l'),(5,'o')] -- myflip zip [1..5] "hello" -- [('h',1),('e',2),('l',3),('l',4),('o',5)] -- myzipWith div (repeat 2) [1..5] -- [2,1,0,0,0] -- flip (myzipWith div) (repeat 2) [1..5] -- [0,1,1,2,2] -- 函数式程序员工具箱 -- 身为函数式程序员,不多须要对单个值求解花费太多心思。更多状况下是,处理一系列的数值、字母或者其余类型的数据 -- 经过转换了这一类集合来求取最后的结果。经常使用的工具函数有:map、filter -- map接受一个取a返回b的函数和一组a值的列表为参数,返回一组b值的列表 mymap:: (a->b) -> [a] -> [b] mymap _ [] = [] mymap f (x:xs) = f x : mymap f xs -- filter 去一个谓词(predicate)和一个列表做为参数 ,返回由列表全部符合该条件的元素组成的列表。 -- 谓词特指判断事物是True或者False的函数,也就是返回布尔值的函数 myfilter :: (a->Bool) -> [a] -> [a] myfilter _ [] = [] myfilter f (x:xs) | f x = x : myfilter f xs | otherwise = myfilter f xs -- mymap (+2) [1..10] -- [3,4,5,6,7,8,9,10,11,12] -- myfilter (>5) [1..10] -- [6,7,8,9,10] -- myfilter (`elem` ['a'..'z']) "Hi, my name is Ausk!" -- "imynameisusk" -- myfilter (`elem` ['A'..'Z']) "Hi, my name is Ausk!" -- "HA" -- myfilter (<15) (myfilter even [1..20]) -- [2,4,6,8,10,12,14] -- [x|x<-[1..20], even x,x<15] -- [2,4,6,8,10,12,14] -- 计算小于1000的全部奇数的平方的和,分别使用括号、列表生成器、函数组合和函数应用符 sum (takeWhile (<1000) (filter odd (map (^2) [1..]))) -- 5456,函数组合 sum (takeWhile (<1000) [x^2| x<-[1..],odd x]) -- 5456,列表生成器 sum . takeWhile (<1000) . filter odd $map (^2) $[1..] -- 5456,函数组合和函数应用符 -- 将天然数的平方根相加,会在何处超过1000? sqrtSum :: Int sqrtSum = length ( takeWhile(<1000) ( scanl1 (+) (map sqrt [1..]) ) ) + 1 -- sum(map sqrt [1..130]) -- 993.6486803921487 -- sum(map sqrt [1..131]) -- 1005.0942035344083 -- 克拉兹序列,定义以下 -- 从任意天然数开始,若是是1则中止;若是是偶数则除以2;若是是奇数则乘以3而后加上1。取得结果并重复过程。 chain :: Integer -> [Integer] chain 1=[1] -- 基准条件 chain n -- 哨兵选择 | even n = n:chain (n `div` 2) | odd n = n:chain (n*3 + 1) -- chain 1 -- [1] -- chain 10 -- [10,5,16,8,4,2,1] -- 映射带有多个参数的函数 let listOfFuncs = map (*) [10..] -- (*10) (*11) (*12) (*13) (*14) (*15) ... (listOfFuncs !!4) 4 -- (*14) 4 = 56 -- Haskell中的Lambda是一次性的匿名函数,以右斜杠开头,后面紧跟着函数的参数列表,参数之间使用空格分割,->以后是函数体,一般使用圆括号括起来。 map (+3) [1..5] -- [4,5,6,7,8] map (\x -> x+3) [1..5] -- [4,5,6,7,8] map (\(a,b) -> a+b) (zip [1..5] [11..15]) -- [12,14,16,18,20] zipWith (\a b -> a*10 + b) [1..] [5,4,3,2,1] -- [15,24,33,42,51] -- 处理列表大多数具备笃定模式。一般会将基准条件设置为空列表,而后引入 (x:xs)模式,再对单个元素和余下的列表作一些事情。 -- 由于这一模式比较常见,Haskell中提供了折叠(fold)函数来使之简化。经过折叠函数将一个数据结构(如列表)归约为单个值。 -- 一个折叠能够从左边或者右边开始折叠,一般取一个二元函数、一个待折叠的列表(和一个初始值),获得归约后的单值。 -- 左折叠(foldl、foldl1)从左侧开始,右折叠(foldr、foldr1)从右侧开始。 -- 使用lambda表达式和foldl进行折叠 suml :: (Num a) => [a] -> a suml xs = foldl (\acc x-> acc + x) 0 xs -- 使用foldl进行折叠 suml2 :: (Num a) => [a] -> a suml2 xs = foldl (+) 0 xs -- 使用lambda表达式和foldr进行折叠 sumr :: (Num a) => [a] -> a sumr xs = foldr (\x acc-> acc + x) 0 xs -- 使用foldr进行折叠 sumr2 :: (Num a) => [a] -> a sumr2 xs = foldr (+) 0 xs -- 使用左折叠实现map mapl :: (a->b) -> [a] -> [b] mapl f xs = foldl(\acc x -> acc ++ [f x]) [] xs -- mapl (+1) [1..5] -- [2,3,4,5,6] -- 使用右折叠实现map mapr :: (a->b) -> [a] -> [b] mapr f xs = foldr(\x acc -> f x : acc) [] xs -- mapr (+1) [1..5] -- [2,3,4,5,6] -- 使用折叠实现一些函数 mymax_foldr1 ::(Ord a) => [a] -> a mymax_foldr1 = foldr1 max -- 使用foldr1函数和max组合,获得求最值的函数 -- mymax [2,4,10,7,3,8,6,1,5,9] -- 10 -- 扫描scan,相似于折叠,不过会将变更记录到一个列表中 mymax_scanr1 :: (Ord a) => [a] ->[a] mymax_scanr1 = scanr1 max -- mymax_scanr1 [2,4,10,7,3,8,6,1,5,9] -- [10,10,10,9,9,9,9,9,9,9] -- 扫描scan,相似于折叠,不过会将变更记录到一个列表中 mymax_scanl1 :: (Ord a) => [a] ->[a] mymax_scanl1 = scanl1 max -- mymax_scanl1 [2,4,10,7,3,8,6,1,5,9] --[2,4,10,10,10,10,10,10,10,10] -- 无限列表折叠,当二元函数不老是须要对第二个参数进行求值如逻辑操做(&& ||)的短路求值 -- 这时,能够利用Haskell的惰性求值操做无限列表 myand ::[Bool] -> Bool myand xs = foldr (&&) True xs myand (repeat False) -- 使用空格的函数调用符是左结合的,如 f a b c 等价于 ((f a b) c) -- 而函数应用符(function application operator)即$的调用是右结合的 -- 函数应用符号能够减小括号,也能够将函数应用转化为函数 -- 即 f $ g $ x 等价于 f$(g$x), 如 sum $ a + b + c 等价于 (sum (a + b + c)) :t ($) -- ($) :: (a -> b) -> a -> b sum $3+4+5 -- 12 map ($3) [ (4+), (5*),(6/),(^2),sqrt] -- [7.0,15.0,2.0,9.0,1.7320508075688772] -- 函数组合function composition,定义是 (f.g)(x) = f(g(x)) -- 也便是先拿参数调用一个函数,而后取结果做为参数调用另外一个函数 -- 进行函数组合的函数是 (.) :t (.) -- (.) :: (b -> c) -> (a -> b) -> a -> c map (\x -> negate $sqrt $ abs x) [-4,16,-9] -- [-2.0,-4.0,-3.0] map (negate . sqrt . abs) [-4,16,-9] -- [-2.0,-4.0,-3.0] sum (replicate 5 (max 3 4) ) -- 20 (sum . replicate 5) (max 3 4) -- 20 sum $ replicate 5 $ max 3 4 -- 20 -- 经过使用函数组合哈数来省略表达式中的大量括号,能够首先找到最里面的函数和参数, -- 在它们前面加上一个$,接着省略其他函数的最后一个参数,经过 (.)组合在一块儿。 replicate 2 ( product ( map (*3) ( zipWith max [1,4,7,9] [3,2,6,10]))) replicate 2 . product . map (*3) $ zipWith max [1,4,7,9] [3,2,6,10] -- 使用函数组合PointFree风格,即省略共同的参数,使得代码更加简洁易读。 -- 让程序员更加倾向于思考函数的组合方式,而不是数据的攒肚和变化。 mysum ::(Num a) => [a] -> a mysum xs = foldr (+) 0 xs -- 等价的point-free风格 mysum ::(Num a) => [a] -> a mysum = foldr (+) 0 -- 原始函数 func :: (Floating a, Integral b, RealFrac a) => a -> b func x = log(sqrt (max 50 x)) -- 等价的point-free风格 func :: (Floating a, Integral b, RealFrac a) => a -> b func = log.sqrt.max 50 ---------------------------------------------------------------------------------------- -- ch06 模块 -- Haskell 中的模块,相似与Python中的module,C++中的namespace,Java中的Package。 -- 模块中能够包含类型和函数的定义,而且容许导出(export)其中的部分定义。 -- 将模块代码分解到模块中,使得代码相互之间较少过多的依赖,也就是使得模块松耦合(loosely coupled),方便管理和重用。 -- 在任意函数定义以前导入模块,import ModuleName (FunctionName) 或者 :m ModuleName -- 要检查函数的定义或者查找函数所在的模块,使用[Hoogle](http://www.haskell.org/hoogle/) -- 学习Haskell的最好方式是查找函数和模块的文档,也能够阅读Haskell各个模块的源代码。 -- 注意当点号位于限定导入的模块名和函数之间且没有空格时标示对模块中函数的引用;不然视为函数的组合。 import qualified Data.Map as M -- 限定导入 Data.Map,定义别名为M,必须使用M.xxx来引用名字 import Data.List hiding (nub) -- 避免导入模块中的某些函数 import Data.List (sort,group,words) -- 导入模块中的特定函数 import Data.List (sort,group,words) wordNums :: String ->[(String,Int)] wordNums = map (\ws ->(head ws, length ws)) . group . sort . words -- wordNums xs = map (\ws ->(head ws, length ws)) (group(sort(words xs) wordNums "it is a good idea, it is very good !" -- [("!",1),("a",1),("good",2),("idea,",1),("is",2),("it",2),("very",1)] -- 干草堆中的缝纫针:查找一个列表(缝纫针,needle)是否在另外一个列表(干草堆,haystack)中 -- 等价的函数是 Data.List.isInPrefixOf import Data.List isIn :: Eq a => [a] -> [a] -> Bool needle `isIn` haystack = any (needle `isPrefixOf`) $tails haystack -- isIn "how" "Hello,how are you ?" -- True -- isIn "hei" "Hello,how are you ?" -- False ---------------------------------------------------------------------------------------- -- 凯撒加密与解密 -- 加密,把字符映射到另外一个字符。首先把字符转化为数字,增长/减小偏移量,再从数字映射到字母。 import Data.Char -- 加密,map (\c -> chr $ ord c + offset) msg myencode :: Int -> String -> String myencode offset = map ( chr . (+offset) . ord ) -- 解密,map (\c -> chr $ ord c + offset) msg mydecode :: Int -> String -> String mydecode offset = map ( chr . (-offset) . ord ) let msg = "how are you" let offset = 10 let encoded = myencode offset msg let decoded = mydecode offset encoded decoded == msg -- True ---------------------------------------------------------------------------------------- -- 实现严格左折叠,不使用惰性求值,防止大量的延迟计算占据内存而致使栈溢出 --myfoldl:: (b -> a -> b) -> b -> [a] -> b myfoldl g initval [] = error "empty list" myfoldl g initval [x] = g initval x --myfoldl g initval all@(x:xs)= myfoldl g (g initval x) xs myfoldl g initval all@(x:xs)= myfoldl g (g initval $head all) $tail all -- myfoldl (+) 0 [1..100000] -- 5000050000 -- Maybe类型: Maybe a类型标示能够为空,能够只含有一个元素 :t Just -- Just :: a -> Maybe a :t Just "Hi" -- Just "Hi" :: Maybe [Char] ---------------------------------------------------------------------------------------- import Data.Char import Data.List -- 计算数字的各位之和 digitSum :: Int -> Int digitSum = sum . map digitToInt . show -- digitSum 49999 -- 40 -- 查找一个数,这个数的各位数字之和等于n findFirstTo :: Int -> Maybe Int findFirstTo n = find (\x -> digitSum x == n ) [1..] -- findFirstTo 40 -- Just 49999 -- 有些数据结构不关心数据的存储顺序,而是将数据按照键值对的形式进行存储,而后按键索值,查找指定键的值。 -- 这样的数据结构可使用关联列表(association list)或者映射map实现,于Python中的dict,C++中的map/unordered_map idBook = [("jj","M72340"),("ff","M72341")] -- 取一个键和一个键值列表做为参数,过滤列表,仅仅保留与键相匹配的元素,取首个键值对,返回其中的值。 -- 一个标准的处理列表的递归函数,基准条件、分隔条件、递归调用都齐全了,这也是典型的折叠模式。 findKey :: (Eq k) => k -> [(k,v)] -> Maybe v findKey key [] = Nothing findKey key ((k,v):xs) | key == x = Just v | otherwise findKey key xs -- [findKey key idBook| key <- ["ff", "ff","ll"] ] -- [Just "M72340",Just "M72341",Nothing] -- 基于折叠模式,替换递归函数。折叠模式简洁明了,一目了然。 findKey :: (Eq k) => k -> [(k,v)] -> Maybe v findKey key xs = foldr (\(k,v) acc -> if key == k then Just v else acc) Nothing xs -- [findKey key idBook| key <- ["ff", "ff","ll"] ] -- [Just "M72340",Just "M72341",Nothing] -- 使用Data.Map数据结构 import qualified Data.Map as M -- 限定导入 Data.Map,定义别名为M,必须使用M.xxx来引用名字 phoneBookToMap ::(Ord k) => [(k,String)] -> M.Map k String phoneBookToMap xs = M.fromListWith add xs where add number1 number2 = number1 ++ ", " ++ number2 phoneBookList = [("jj","M72340"),("jj","Q33361"),("ff","M72341")] phoneBookMap = phoneBookToMap phoneBookList -- fromList [("ff","M72341"),("jj","Q33361, M72340")] newPhoneBookMap = M.insert "ff" "XX-XXX" phoneBookMap -- fromList [("ff","XX-XXX"),("jj","Q33361, M72340")] M.size phoneBookMap -- 2 M.lookup "jj" phoneBookMap --Just "Q33361, M72340" M.lookup "jia" phoneBookMap --Nothing -- Set import qualified Data.Set as Set let text1 = "Are they so different?" let text2 = "Yeah, they are so different!" let set1 = Set.fromList text1 -- fromList " ?Adefhinorsty" let set2 = Set.fromList text2 -- fromList " !,Yadefhinorsty Set.intersection set1 set2 -- fromList " defhinorsty" Set.difference set1 set2 -- fromList "?A" Set.difference set2 set1 -- fromList "!,Ya" Set.union set1 set2 -- fromList " !,?AYadefhinorstyset1 Set.insert 4 $ Set.fromList [9,3,8,1] -- fromList [1,3,4,8,9] Set.delete 4 $ Set.fromList [3,4,5,4,3,4,5] -- fromList [3,5] Set.fromList [2,3,4] `Set.isSubsetOf` Set.fromList [1,2,3,4,5] -- True ---------------------------------------------------------------------------------------- -- 自定义数据类型和类型别名 -- 汽车Car,带类型说明的数据结构,及相应的操做 data Car = Car{ company::String, model::String, year::Int } deriving (Show) tellCar :: Car -> String tellCar (Car {company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y -- 一周七天,继承类型 data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Show, Read, Bounded, Enum) -- 递归数据结构 -- 树的构造和操做 data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq) singleton :: a -> Tree a singleton x = Node x EmptyTree EmptyTree treeInsert :: (Ord a) => a -> Tree a -> Tree a treeInsert x EmptyTree = singleton x treeInsert x (Node a left right) | x == a = Node x left right | x < a = Node a (treeInsert x left) right | x > a = Node a left (treeInsert x right) treeElem :: (Ord a) => a -> Tree a -> Bool treeElem x EmptyTree = False treeElem x (Node a left right) | x == a = True | x < a = treeElem x left | x > a = treeElem x right test = do let stang = Car{company = "Ford", model = "Mustang", year=1967} print $ tellCar stang {- show Wednesday read "Saturday" :: Day Monday `compare` Wednesday Monday == Monday [minBound .. maxBound] :: [Day] succ Monday pred Saturday -} let nums = [8,6,4,1,7,3,5] let numsTree = foldr treeInsert EmptyTree nums print $ numsTree print $ 3 `treeElem` numsTree print $ 13 `treeElem` numsTree ---------------------------------------------------------------------------------------- {- -- hello.hs -- 功能:学习输入和输出操做 shell > ghc --make ./hello.hs shell > ./hello -} import System.IO main = do print "What's your name?" name <- getLine putStrLn $ "Hey " ++ name ++ ", you rock!" sequence $ map print [3..5] mapM print [3..5] -- mapM取一个函数和一个列表做为参数,而后将函数映射到列表上,最后将结果应用与sequence。 mapM_ print [3..5] -- mapM_与mapM相似,不过不保存结果。 -- 使用 openFile打开文件,hGetContents读取文件,hClose关闭文件 infile_handle <- openFile "hello.hs" ReadMode contents <- hGetContents infile_handle putStr contents hClose infile_handle -- 使用withFile确保文件操做后,文件的句柄必定被关闭 withFile "hello.hs" ReadMode (\infile_handle -> do contents <- hGetContents infile_handle putStr contents) ---------------------------------------------------------------------------------------- -- ch10 函数式地解决问题 -- 经常使用的中缀表达式,须要用括号改变优先级别。而使用逆波兰表达式(reverse polish natation, RPN), -- 操做符在操做数的后面。解析表达式子的时候,遇到数字时将数字压入栈, -- 遇到符号时弹出两个数字并使用符号操做,而后将结果入栈。表达式结尾时,栈里面仅剩下一个数字,也就是表达式的值。 import Data.List solveRPN :: String -> Float solveRPN = head . foldl foldingFunction [] . words where foldingFunction (x:y:ys) "*" = (x * y):ys foldingFunction (x:y:ys) "+" = (x + y):ys foldingFunction (x:y:ys) "-" = (y - x):ys foldingFunction (x:y:ys) "/" = (y / x):ys foldingFunction (x:y:ys) "^" = (y ** x):ys foldingFunction (x:xs) "ln" = log x:xs foldingFunction xs "sum" = [sum xs] foldingFunction xs numberString = read numberString:xs {- -- http://learnyouahaskell.com/for-a-few-monads-more readMayBe ::(Read a) => String -> Maybe a readMayBe stmt = case reads stmt of [(x,"")] -> Just x _ -> Nothing foldingFunction2 :: [Double] -> String -> Maybe [Double] foldingFunction2 (x:y:ys) "+" = (x + y):ys foldingFunction2 (x:y:ys) "-" = (y - x):ys foldingFunction2 (x:y:ys) "/" = (y / x):ys foldingFunction2 (x:y:ys) "^" = (y ** x):ys foldingFunction2 (x:xs) "ln" = log x:xs foldingFunction2 xs "sum" = [sum xs] foldingFunction xs numberString = liftM (:xs) $ readMayBe numberString solveRPN2 :: String -> Maybe Double solveRPN2 stmt = do [result] <- foldM foldingFunction2 [] $ words stmt return result -} ----------------------------------------------------------------------------------- {- - 计算最快的路线 # A === 50 == A1 === 5 === A2 === 40 === A3 === 10 === A4 # || || || || # 30 20 25 00 # || || || || # B === 10 == B1 === 90 === B2 === 2 === B3 === 8 === B4 求分别从A和B出发到达A4和B4的最短路径 解决方案: 每次检查三条路线的通行时间:A路上一段路程,B路上一段路程,以及链接两个路口的C小路。 称它们为一个地段section。所以将道路标示为4个地段: > 50 30 10 > 5 20 90 > 40 25 2 > 10 0 8 定义道路数据类型 data Section = Section { getA :: Int, getB :: Int, getC :: Int } deriving (Show) type RoadSystem = [Section] 引入标签Label表示一个枚举类型(A、B或者C),以及一个类型别名Path。 data Label = A | B | C deriving (Show) type Path = [(Label, Int)] 在求解时,处理的列表同时维护抵达当前地段A的最佳路径和抵达B的最佳路径。遍历的时候累加这两条最佳路径,是一个左折叠。 折叠的步骤是,根据抵达当前地段A路和B路的最佳路线和下一个地段的信息,分别计算到达下一个地段A和下一个地段B的最佳路线。 初始地段A、B路的最佳路线都是[],检查地段 Section 50 10 30,推断出到达A1和B1的最佳路线分别是[(B,10),(C,30)]和[(B,10)]。 重复这一计算步骤,直处处理完毕。写成函数的形式,参数是一对路径和一个地段,返回到达下一个地段的一条新的最佳路径。 roadStep :: (Path, Path) -> Section -> (Path, Path) -} import Data.List --在任何函数开始以前道路模块 import System.IO -- 导入 IO 模块 -- 自定义数据类型和类型别名 data Section = Section { getA :: Int, getB :: Int, getC :: Int } deriving (Show) type RoadSystem = [Section] data Label = A | B | C deriving (Show) type Path = [(Label, Int)] -- 构建道路 heathrowToLondon :: RoadSystem heathrowToLondon = [Section 50 10 30, Section 5 90 20, Section 40 2 25, Section 10 8 0] -- 根据当前路段A和B路的最佳路线以及下一个Section的信息,求解获得下一个路段A路和B路的最佳路线。 roadStep :: (Path, Path) -> Section -> (Path, Path) roadStep (pathA, pathB) (Section a b c) = let priceA = sum $ map snd pathA priceB = sum $ map snd pathB forwardPriceToA = priceA + a crossPriceToA = priceB + b + c forwardPriceToB = priceB + b crossPriceToB = priceA + a + c newPathToA = if forwardPriceToA <= crossPriceToA then (A,a):pathA else (C,c):(B,b):pathB newPathToB = if forwardPriceToB <= crossPriceToB then (B,b):pathB else (C,c):(A,a):pathA in (newPathToA, newPathToB) -- 求解最佳路径 optimalPath :: RoadSystem -> Path optimalPath roadSystem = let (bestAPath, bestBPath) = foldl roadStep ([],[]) roadSystem in if sum (map snd bestAPath) <= sum (map snd bestBPath) then reverse bestAPath else reverse bestBPath -- 将一个列表分割成为一些等长度的列表 groupsOf :: Int -> [a] -> [[a]] groupsOf 0 _ = undefined groupsOf _ [] = [] groupsOf n xs = take n xs : groupsOf n (drop n xs) -- 输入和输出操做 shell > ghc --make ./hello.hs shell > ./hello testCalcBestPath = do print "The default patch:" ++ show heathrowToLondon print "The optimal path:" ++ show (optimalPath heathrowToLondon) {- echo -e "50\n10\n30\n5\n90\n20\n40\n2\n25\n10\n8\n0" > path.txt runhaskell haskell.hs <path.txt ghc --make -O xxx.hs shell > ./haskell < path.txt print "Get path from file (runhaskell XXX.hs < path.txt):" -} -- 直接从文件中读取路径 withFile "path.txt" ReadMode (\infile_handle -> do contents <- hGetContents infile_handle putStr contents let threeTupleList = groupsOf 3 (map read $ lines contents) roadSystem = map (\[a,b,c] -> Section a b c) threeTupleList path = optimalPath roadSystem putStrLn $ "The best path to take is: " ++ show path putStrLn $ "The best path to take is: " ++ (concat $ map (show . fst) path ) putStrLn $ "The price of the path is: " ++ show (sum $ map snd path) ) main = do -- -- 调用测试函数 -- testCalcBestPath print $ "Hello, Haskell!" {- print "What's your name?" name <- getLine putStrLn $ "Hey " ++ name ++ ", you rock!" sequence $ map print [3..5] mapM print [3..5] -- mapM取一个函数和一个列表做为参数,而后将函数映射到列表上,最后将结果应用与sequence。 mapM_ print [3..5] -- mapM_与mapM相似,不过不保存结果。 -- 使用 openFile打开文件,hGetContents读取文件,hClose关闭文件 infile_handle <- openFile "hello.hs" ReadMode contents <- hGetContents infile_handle putStr contents hClose infile_handle -- 使用withFile确保文件操做后,文件的句柄必定被关闭 withFile "hello.hs" ReadMode (\infile_handle -> do contents <- hGetContents infile_handle putStr contents) -}