Haskell语言学习笔记
学习Haskell函数式编程语言,第一部分,基础知识和基本函数语法。
1 基础知识
1.1 语法清单
let a = 7
:let 用于定义常量,常量名、函数名必须首字母小写
1.2 List、德州区间和 List Comprehension
1.2.1 List
List是一种单类型的数据结构,可以用来存储多个类型相同的元素。
1 | ghci> let lostNumbers = [4,8,15,16,23,48] |
如上,一个List由方括号括起,其中的元素用逗号分隔开来。
"hello" ++ " " ++ "world"
得到"hello world"
:++运算符,用于连接两个List5:[1,2,3,4,5]
得到[5,1,2,3,4,5]
:使用:运算符往一个List前端插入元素"Steve Buscemi" !! 6
得到B
:使用!!运算符按照索引取得List中的元素- 用> < >= = 等符号进行List间的比较
- 其余常用函数参见 第二章 Haskell入门_w3cschool
1.2.2 德州区间
区间(Range)是构造 List 方法之一,而其中的值必须是可枚举的,如1、2、3,A、B、C等。
1 | ghci> [1..20] --简单的区间 |
1.2.3 List Comprehension
List Comprehension能够更加方便自由地生成List,类似于集合的描述法:
1 | ghci> [x*2 | x <- [1..10]] --前10个整数 |
1.3 Tuple
Tuple (元组)要求你对需要组合的数据的数目非常的明确,它的类型取决于其中项的数目与其各自的类型。 Tuple 中的项由括号括起,并由逗号隔开。另外,Tuple 中的项不必为同一类型,在 Tuple 里可以存入多类型项的组合。
1 | ghci> fst (8,11) --返回一个序对的首项 |
1.4 类型和类型类
Haskell 的类型必须是首字母大写
使用:t命令后跟任何可用的表达式,可以检测表达式的类型
1
2
3
4
5
6
7
8
9
10ghci> :t 'a'
'a' :: Char
ghci> :t True
True :: Bool
ghci> :t "HELLO!"
"HELLO!" :: [Char] --[char] <=> String
ghci> :t (True, 'a')
(True, 'a') :: (Bool, Char)
ghci> :t 4 == 5
4 == 5 :: Bool定义函数时,可以定义参数和返回值类型,参数之间以及参数和返回值之间均使用
->
分隔1
2addThree :: Int -> Int -> Int -> Int --三个参数,一个返回值
addThree x y z = x + y + z
2 函数语法
2.1 函数声明方法
函数声明:先函数名,后跟由空格分隔的参数表。声明中一定要在
=
后面定义函数的行为1
doubleMe x = x + x --功能:将一个数字乘以2
可以在其他函数中调用自己编写的函数,不用考虑函数出现的先后顺序
1
doubleUs x y = doubleMe x + doubleMe y --功能:接收两个参数,返回它们的和的2倍
为函数编写明确的类型声明是一个好习惯
1
2
3
4
5
6
7--功能:过滤大写字母
removeNonUppercase :: [Char] -> [Char]
removeNonUppercase st = [ c | c st, c `elem` ['A'..'Z']]
--功能:三个整数相加
addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z
2.2 模式匹配
模式匹配通过检查数据的特定结构来检查其是否匹配,并按模式从中取得数据。类似于switch...case...
结构。
1 | sayMe :: (Integral a) => a -> String |
【注意】
- 不可以在模式匹配中使用
++
2.2.1 对Tuple使用模式匹配
1 | --不会模式匹配的时候 |
用模式匹配实现针对三元组的first、second、third函数
1 | first :: (a, b, c) -> a |
2.2.2 在List Comprehension中使用模式匹配
1 | ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)] |
一旦模式匹配失败,它就简单挪到下个元素。
2.2.3 对List使用模式匹配
用[]
或:
来匹配List。例如:
x:xs
模式,可以将list的头部绑定为x,尾部绑定为xs,但这种模式只能匹配长度大于等于1的List,因此对于空的List需要进行特殊判断。x:y:z:xs
模式,可以将List的前三个元素都绑定到变量中,但只能匹配长度大于等于3的List,因此对于长度小于3的List需要进行特殊判断。
1 | --实现一个自己的 head 函数 |
2.2.4 as模式
所谓as
模式,就是将一个名字和@
置于模式前,可以在按模式分割什么东西时仍保留对其整体的引用。如这个模式xs@(x:y:ys)
,它会匹配出与x:y:ys
对应的东西,同时你也可以方便地通过xs
得到整个list,而不必在函数体中重复x:y:ys
。
1 | --代码-- |
2.3 门卫
1 | --一个用到了门卫的函数-- |
门卫由跟在函数名及参数后面的竖线标志,通常他们都是靠右一个缩进排成一列。一个门卫就是一个布尔表达式,如果为真,就使用其对应的函数体。如果为假,就送去见下一个门卫,如之继续。
如果一个函数的所有门卫都没有通过(而且没有提供otherwise作万能匹配),就转入下一模式。这便是门卫与模式契合的地方。如果始终没有找到合适的门卫或模式,就会发生一个错误。
2.4 where绑定
1 | --使用where关键字从而避免重复工作 |
- where关键字跟在门卫后面(最好是与竖线缩进一致)。
- 可以定义多个名字和函数,这些名字对每个门卫都是可见的。
- 函数在where绑定中定义的名字只对本函数可见,因此我们不必担心它会污染其他函数的命名空间。
where绑定不会在多个模式中共享。如果你在一个函数的多个模式中重复用到同一名字,就应该把它置于全局定义之中。
where绑定也可以使用模式匹配
1
2
3...
where bmi = weight / height ^ 2
(skinny, normal, fat) = (18.5, 25.0, 30.0)
2.5 let绑定
1 | --依据半径和高度求圆柱体表面积-- |
let的格式为let [bindings] in [expressions]
。在let中绑定的名字仅对in部分可见。let里面定义的名字也得对齐到一列。
note:let绑定和where绑定的区别:
**(1)where绑定是个语法结构,let绑定是个表达式,可以随处安放(就像if语句一样)。** **(2)let定义域限制的相当小,因此不能在多个门卫中使用。where跟在函数体后面,主函数体距离类型声明近一些,会更易读。**
let可以定义局部函数
1
2ghci> [let square x = x * x in (square 5, square 3, square 2)]
[(25,9,4)]若要在一行中绑定多个名字,可以用分号将其分开
1
2ghci> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar = "there!" in foo ++ bar)
(6000000,"Hey there!")可以在let绑定中使用模式匹配。这在从Tuple取值之类的操作中很方便。
1
2ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100
600也可以把let绑定放到List Comprehension中。我们重写下那个计算bmi值的函数,用个let替换掉原先的where。
1
2calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) xs, let bmi = w / h ^ 2]
2.6 case表达式
模式匹配本质上不过就是case语句的语法糖而已。这两段代码就是完全等价的:
1 | head' :: [a] -> a |
1 | head' :: [a] -> a |
case表达式的语法:
1 | case expression of pattern -> result |
note:case表达式和模式匹配的区别:
函数参数的模式匹配只能在定义函数时使用,而case表达式可以用在任何地方
3 关于函数式编程和Haskell的参考文章
Clojure和Haskell——深度学习中的函数式语言之美-InfoQ
上学期看崔毅东老师的C++课程时,依稀记得老师提到了这门语言,于是大概学习了一下,第一次接触函数式编程的思想,蛮有趣但还是比较懵,之后再找找实战玩玩看。