# 0. 各就各位,预备!
## 0.1. 算数运算
### 0.1.1. 加减乘除
```ghci
ghci> 2 + 15
17
ghci> 49 * 100
4900
ghci> 1892 - 1472
420
ghci> 5 / 2
2.5
```
### 0.1.2. 括号
```ghci
ghci> (50 * 100) - 4999
1
ghci> 50 * 100 - 4999
1
ghci> 50 * (100 - 4999)
-244950
```
### 0.1.3. 负数
```ghci
ghci> -3 * 5
-15
ghci> 5 * (-3)
-15
```
执行 `5 * -3` GHCi 会报错,负数最好用括号括起来。
## 0.2. 逻辑运算
### 0.2.1. 布尔值
Haskell 的布尔值为 `True` 和 `False`。
### 0.2.2. 与或非
```ghci
ghci> True && False
False
ghci> True && True
True
ghci> False || True
True
ghci> not False
True
ghci> not (True && True)
False
```
### 0.2.3. 相等 & 不相等
```ghci
ghci> 5 == 5
True
ghci> 1 == 0
False
ghci> 5 /= 5
False
ghci> 5 /= 4
True
ghci> "hello" == "hello"
True
```
以上双目运算符都要求参与运算的两个参数类型相同。
注意,整数可被看作是浮点数。
```ghci
ghci> 5 == 5.0
True
ghci> 5 + 4.0
9.0
```
# 1. 调用函数
## 1.1. 中缀函数(infix function)
使用时用两个参数将它夹在中央的函数,如双目运算符。
## 1.2. 前缀函数(prefix function)
使用时函数名放在最前面的函数,大部分函数都属于前缀函数。
### 1.2.1. 前缀函数举例
`succ`:取得参数的后继,前提是它要有明确的后继。
```ghci
ghci> succ 8
9
```
- `min`:返回两个可比较大小的参数的较小者。
- `max`:返回两个可比较大小的参数的较大者。
```ghci
ghci> min 9 10
9
ghci> min 3.4 3.2
3.2
ghci> max 100 101
101
```
## 1.3. 以中缀形式调用函数
`div`:求两个整数的商。
```ghci
ghci> div 92 10
9
```
这样写不容易理解,有两个参数的函数可将函数名用 `` ` `` 括起,然后以中缀形式调用。
```ghci
ghci> 92 `div` 10
9
```
# 2. 小朋友的第一个函数
## 2.1. 声明函数
新建脚本 `baby.hs`:
```hs
doubleSmallNumber x =
if x > 100
then x
else 2 * x
```
调用 `baby.hs` 中声明的函数。
```ghci
ghci> :l baby
[1 of 1] Compiling Main ( baby.hs, interpreted )
Ok, one module loaded.
ghci> doubleSmallNumber 9
18
ghci> doubleSmallNumber 101
101
```
## 2.2. if 语句
与其他语言不同,Haskell 中 `if` 语句的 `else` 部分是不可省略的。
Haskell 中的 `if` 是一个必然返回结果的表达式(expression),而非语句(statement)。
## 2.3. 函数名
函数名不能以大写字母开头,函数名中 `'` 属于合法字符。
# 3. 列表入门
在 Haskell 中,列表是一种单类型的(homogeneous)数据结构。
在 GHCi 中,可使用 `let` 关键字来定义一个常量。
```ghci
ghci> let lostNumbers = [4,8,15,16,23,42]
ghci> lostNumbers
[4,8,15,16,23,42]
```
## 3.1. 拼接列表
拼接两个列表可以通过 `++` 运算符实现。
```ghci
ghci> [1,2,3,4] ++ [9,10,11,12]
[1,2,3,4,9,10,11,12]
ghci> "hello" ++ " " ++ "world"
"hello world"
ghci> ['w','o'] ++ ['o','t']
"woot"
```
Haskell 中的字符串实际上就是一组字符组成的列表。
`"hello"` 只是 `['h','e','l','l','o']` 的语法糖而已。
因此,可以使用处理列表的函数来对字符串进行操作。
使用 `++` 运算符时,Haskell 会遍历运算符左边的整个列表。
使用 Cons 运算符 `:` 可将一个元素插入列表头部。
```ghci
ghci> 'A':" SMALL CAT"
"A SMALL CAT"
ghci> 5:[1,2,3,4,5]
[5,1,2,3,4,5]
```
值得注意的是,`[1,2,3]` 实际上是 `1:2:3:[]` 的语法糖。
## 3.2. 访问列表中的元素
可以使用 `!!` 运算符按照索引取得列表中的元素,下标从 0 开始。
```ghci
ghci> "Steve Buscemi" !! 6
'B'
ghci> [9.4,33.2,96.2,11.2,23.25] !! 1
33.2
```
## 3.3. 嵌套列表
列表中的列表可以是不同长度的,但必须类型相同。
```ghci
ghci> let b = [[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b ++ [[1,1,1,1]]
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3],[1,1,1,1]]
ghci> [6,6,6]:b
[[6,6,6],[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b !! 2
[1,2,2,3,4]
```
## 3.4. 比较列表
只要列表内的元素是可以比较的,那就可以比较两个列表的字典序大小。
```ghci
ghci> [3,2,1] > [2,1,0]
True
ghci> [3,2,1] > [2,10,100]
True
ghci> [3,4,2] < [3,4,3]
True
ghci> [3,4,2] > [2,4]
True
ghci> [3,4,2] == [3,4,2]
True
```
## 3.5. 更多列表操作
- `head`:返回一个列表的头部,也就是列表的第一个元素。
- `tail`:返回一个列表的尾部,也就是列表除去头部之后的部分。
- `last`:返回一个列表的最后一个元素。
- `init`:返回一个列表除去最后一个元素的部分。
```ghci
ghci> head [5,4,3,2,1]
5
ghci> tail [5,4,3,2,1]
[4,3,2,1]
ghci> last [5,4,3,2,1]
1
ghci> init [5,4,3,2,1]
[5,4,3,2]
```
注意,以上四种操作不要用到空列表上。
`length`:返回一个列表的长度。
```ghci
ghci> length [5,4,3,2,1]
5
```
`null`:检查一个列表是否为空。
```ghci
ghci> null [1,2,3]
False
ghci> null []
True
```
`reverse`:将一个列表反转。
```ghci
ghci> reverse [5,4,3,2,1]
[1,2,3,4,5]
```
- `take`:返回一个列表中指定的前几个元素。
- `drop`:删除一个列表中指定的前几个元素。
```ghci
ghci> take 3 [5,4,3,2,1]
[5,4,3]
ghci> take 1 [3,9,3]
[3]
ghci> take 5 [1,2]
[1,2]
ghci> take 0 [6,6,6]
[]
ghci> drop 3 [8,4,2,1,5,6]
[1,5,6]
ghci> drop 0 [1,2,3,4]
[1,2,3,4]
ghci> drop 100 [1,2,3,4]
[]
```
- `maximum`:返回一个列表中最大的元素。
- `minimum`:返回一个列表中最小的元素。
```ghci
ghci> maximum [1,9,2,3,4]
9
ghci> minimum [8,4,2,1,5,6]
1
```
- `sum`:返回一个列表中所有元素的和。
- `product`:返回一个列表中所有元素的积。
```ghci
ghci> sum [5,2,1,6,3,2,5,7]
31
ghci> product [6,2,1,2]
24
ghci> product [1,2,5,6,7,9,2,0]
0
```
`elem`:判断一个元素是否包含于一个列表,通常以中缀形式调用。
```ghci
ghci> 4 `elem` [3,4,5,6]
True
ghci> 10 `elem` [3,4,5,6]
False
```
# 4. 得州区间
区间(range)是构造列表的方法之一,其中的值必须是可枚举的(可排序的)。
## 4.1. 使用区间
```ghci
ghci> [1..20]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
ghci> ['a'..'z']
"abcdefghijklmnopqrstuvwxyz"
ghci> ['k'..'z']
"klmnopqrstuvwxyz"
```
以上写法是不指定区间步长的写法。
Haskell 会先构造一个空列表,然后从区间下限开始增长至区间上限。
## 4.2. 指定步长
通过前两个元素的间隔指定步长。
```ghci
ghci> [2,4..20]
[2,4,6,8,10,12,14,16,18,20]
ghci> [3,6..20]
[3,6,9,12,15,18]
```
## 4.3. 无限列表
实际上,可以不指定区间上限。
```ghci
ghci> take 24 [13,26..]
[13,26,39,52,65,78,91,104,117,130,143,156,169,182,195,208,221,234,247,260,273,286,299,312]
```
- `cycle`:返回一个由指定列表与自身不断拼接而成的无限列表。
- `repeat`:返回一个仅包含指定值的无限列表。
- `replicate`:返回一个指定长度的包含相同指定元素的列表。
```ghci
ghci> take 10 (cycle [1,2,3])
[1,2,3,1,2,3,1,2,3,1]
ghci> take 12 (cycle "LOL ")
"LOL LOL LOL "
ghci> take 10 (repeat 5)
[5,5,5,5,5,5,5,5,5,5]
ghci> replicate 3 10
[10,10,10]
```
由于浮点数的精度有限,在区间中使用浮点数要格外小心。
否则,会得到如下的糟糕结果。
```ghci
ghci> [0.1,0.3..1]
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]
```
# 5. 我是列表推导式
列表推导式(list comprehension)是一种过滤、转换或者列表组合的方法。
从一个列表中筛选出符合特定谓词(predicate)的元素的操作称为过滤(filter)。
## 5.1. 使用列表推导式
```ghci
ghci> [x * 2 | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]
ghci> [x * 2 | x <- [1..10], x * 2 >= 12]
[12,14,16,18,20]
```
`mod`:求两个整数的余数。
```ghci
ghci> [x | x <- [50..100], x `mod` 7 == 3]
[52,59,66,73,80,87,94]
```
`odd`:判断一个数是否为奇数。
```ghci
ghci> let boomBangs xs = [if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]
ghci> boomBangs [7..13]
["BOOM!","BOOM!","BANG!","BANG!"]
```
## 5.2. 多项谓词
```ghci
ghci> [x | x <- [10..20], x /= 13, x /= 15, x /= 19]
[10,11,12,14,16,17,18,20]
```
## 5.3. 从多个列表中取元素
```ghci
ghci> [x + y | x <- [1,2,3], y <- [10,100,1000]]
[11,101,1001,12,102,1002,13,103,1003]
ghci> [x * y | x <- [2,5,10], y <- [8,10,11]]
[16,20,22,40,50,55,80,100,110]
ghci> [x * y | x <- [2,5,10], y <- [8,10,11], x * y > 50]
[55,80,100,110]
```
## 5.4. 编写一些有用的函数
```hs
length' xs = sum [1 | _ <- xs]
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]
```
## 5.5. 嵌套的列表推导式
`odd`:判断一个数是否为偶数。
```ghci
ghci> let xxs = [[1,3,5,2,3,1,2,4,5],[1,2,3,4,5,6,7,8,9],[1,2,4,2,1,6,3,1,3,2,3,6]]
ghci> [[x | x <- xs, even x] | xs <- xxs]
[[2,2,4],[2,4,6,8],[2,4,2,6,2,6]]
```
# 6. 元组
元组(tuple)允许将多个异构的值组合成为一个单一的值。
## 6.1. 使用元组
```ghci
ghci> (1,3)
(1,3)
ghci> (3,'a',"hello")
(3,'a',"hello")
ghci> (50,50.4,"hello",'b')
(50,50.4,"hello",'b')
```
对于元组的列表,与嵌套列表不同的是列表中的元组长度和类型都必须相同。
## 6.2. 使用序对
长度为 2 的元组也称作序对(pair)。
- `fst`:返回一个序对的首项。
- `snd`:返回一个序对的尾项。
```ghci
ghci> fst (8,11)
8
ghci> fst ("Wow", False)
"Wow"
ghci> snd (8,11)
11
ghci> snd ("Wow", False)
False
```
`zip`:将两个列表交叉配对,生成一组序对的列表。
```ghci
ghci> zip [1,2,3,4,5] [5,5,5,5,5]
[(1,5),(2,5),(3,5),(4,5),(5,5)]
ghci> zip [1..5] ["one","two","three","four","five"]
[(1,"one"),(2,"two"),(3,"three"),(4,"four"),(5,"five")]
```
较长的列表会在中间断开,至较短的列表结束为止。
由于 Haskell 是惰性的,也可以使用 `zip` 组合有限的和无限的列表。
```ghci
ghci> zip [5,3,2,6,2,7,2,5,4,6,6] ["im","a","turtle"]
[(5,"im"),(3,"a"),(2,"turtle")]
ghci> zip [1..] ["apple","orange","cherry","mango"]
[(1,"apple"),(2,"orange"),(3,"cherry"),(4,"mango")]
```
## 6.3. 找直角三角形
找到所有满足下列条件的直角三角形:
- 三边长度皆为整数。
- 三边长度皆小于等于 10。
- 周长为 24。
`^`:指数运算符。
```ghci
ghci> let rightTriangles' = [(a,b,c) | c <- [1..10], a <- [1..c], b <- [1..a], a ^ 2 + b ^ 2 == c ^ 2, a + b + c == 24]
ghci> rightTriangles'
[(8,6,10)]
```

《Haskell 趣学指南》学习笔记 - 第 1 章