# 0. 相信类型
强大的类型系统是 Haskell 的秘密武器。
Haskell 中一切皆有类型,支持类型推导(type inference)。
编译器在编译时可以得到较多的信息来检查错误。
# 1. 显式类型声明
## 1.1. 使用 GHCi 检查表达式的类型
使用 `:t` 命令,后跟任何合法的表达式。
```ghci
ghci> :t 'a'
'a' :: Char
ghci> :t True
True :: Bool
ghci> :t "HELLO!"
"HELLO!" :: [Char]
ghci> :t (True, 'a')
(True, 'a') :: (Bool, Char)
ghci> :t 4 == 5
4 == 5 :: Bool
```
`::` 后即表达式的类型。
凡是明确的类型,其首字母必为大写。
## 1.2. 函数的类型
函数也有类型,编写函数时给一个显式的类型声明是一个好习惯。
```hs
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. Haskell 的常见类型
- `Int`:整数类型,是有界的(bounded)。
- `Integer`:整数类型,是无界的。
GHC 规定 `Int` 类型的界限与机器相关,例如:
在采用 64 位 CPU 的机器上,`Int` 的范围一般为 $\left[-2^{63},2^{63}-1\right]$。
而 `Integer` 类型可以存放非常巨大的数字,但效率不如 `Int` 高。
```hs
factorial :: Integer -> Integer
factorial n = product [1 .. n]
```
```ghci
ghci> factorial 50
30414093201713378043612608166064768844377641568960512000000000000
```
- `Float`:浮点类型,单精度。
- `Double`:浮点类型,双精度。
```hs
circumference :: Float -> Float
circumference r = 2 * pi * r
circumference' :: Double -> Double
circumference' r = 2 * pi * r
```
```ghci
ghci> circumference 4.0
25.132742
ghci> circumference' 4.0
25.132741228718345
```
`Bool`:布尔(Boolean)类型,只有 `True` 和 `False` 两种值。
`Char`:字符(character)类型,表示一个 Unicode 字符。
元组也是类型,但它们的类型取决于其中项的类型及数目。
理论上可以有无限种元组类型,实际上元组的项的数目最大为 62。
空元组也是一种类型,只有一种值 `()`。
# 3. 类型变量
有时让一些函数处理多种类型将更合理。
例如,`head` 函数可以取多种元素的列表作为参数。
检查 `head` 函数的类型声明:
```ghci
ghci> :t head
head :: [a] -> a
```
这里的 `a` 是小写字母,所以不是一个类型。
实际上 `a` 是一个类型变量(type variable),它可以是任何类型。
通过类型变量,可以在类型安全(type safe)的前提下编写能处理多种类型的函数。
这一点与其他语言中的泛型(generic)很相似,但 Haskell 中要更为强大。
使用了类型变量的函数被称作多态函数(polymorphic function)。
在命名上,类型变量和一般变量没有区别,但通常使用单个字符。
最后检查 `fst` 函数的类型声明:
```ghci
ghci> :t fst
fst :: (a, b) -> a
```
`a` 和 `b` 是不同的类型变量,但并非特指 `a` 和 `b` 表示的类型不同。
这就是 `fst` 函数能处理任何类型的序对的原因。
# 4. 类型类入门
类型类(typeclass)是定义行为的接口。
如果一个类型是某类型类的实例(instance),那它必须实现了该类型类所描述的行为。
以定义相等性的类型类为例,检查 `==` 运算符的类型声明:
```ghci
ghci> :t (==)
(==) :: Eq a => a -> a -> Bool
```
> 像 `==` 运算符这种皆以特殊字符命名的函数默认为中缀函数。
以中缀函数作为参数时必须用用括号将它括起。
注意新符号 `=>`,它的左侧为类型约束(type constraint)。
`Eq a` 表示类型变量 `a` 的类型为 `Eq` 类型类的实例。
不要将 Haskell 的类型类和面向对象类语言中类(Class)的概念混淆。
接下来观察几个 Haskell 中最常见的类型类。
## 4.1. `Eq` 类型类
`Eq` 类型类用于可判断相等性的类型。
`Eq` 类型类要求它的实例必须实现 `==` 和 `/=` 两个函数。
Haskell 中所有的标准类型都是 `Eq` 类型类的实例。
```ghci
ghci> 5 == 5
True
ghci> 5 /= 5
False
ghci> 'a' == 'a'
True
ghci> "Ho Ho" == "Ho Ho"
True
ghci> 3.432 == 3.432
True
```
## 4.2. `Ord` 类型类
`Ord` 类型类用于可比较大小的类型类。
检查 `>` 运算符的类型声明:
```ghci
ghci> :t (>)
(>) :: Ord a => a -> a -> Bool
```
与 `==` 运算符的类型很相似。
目前提到的除函数以外的所有类型都是 `Ord` 类型类的实例。
`Ord` 类型类中包含了所有标准的比较函数,如 `<`、`>`、`<=` 和 `>=` 等。
`compare` 也是一个比较函数。
```ghci
ghci> :t compare
compare :: Ord a => a -> a -> Ordering
```
`Ordering` 类型有 `GT`、`LT` 和 `EQ` 三种值,分别表示大于、小于和等于。
```ghci
ghci> "Abrakadabra" < "Zebra"
True
ghci> "Abrakadabra" `compare` "Zebra"
LT
ghci> 5 >= 2
True
ghci> 5 `compare` 3
GT
ghci> 'b' > 'a'
True
```
## 4.3. `Show` 类型类
`Show` 类型类的实例为可以表示为字符串的类型。
目前提到的除函数以外的所有类型都是 `Show` 类型类的实例。
操作 `Show` 类型类实例的函数中,最常用的是 `show`。
`show`:将 `Show` 类型类实例的参数转为字符串。
```ghci
ghci> show 3
"3"
ghci> show 5.334
"5.334"
ghci> show True
"True"
```
## 4.4. `Read` 类型类
`Read` 类型类与 `Show` 类型类相反。
目前提到的除函数以外的所有类型都是 `Read` 类型类的实例。
操作 `Read` 类型类实例的函数中,最常用的是 `read`。
`read`:将一个字符串转为 `Read` 类型类实例的值。
```ghci
ghci> read "True" || False
True
ghci> read "8.2" + 3.8
12.0
ghci> read "5" - 2
3
ghci> read "[1, 2, 3, 4]" ++ [3]
[1,2,3,4,3]
```
注意,上面调用 `read` 函数后都用所得结果进行了进一步运算,GHCi 借此来辨认类型。
如果直接调用 `read` 函数,如执行 `read "4"`,GHCi 会报错。
检查 `read` 函数的类型声明:
```ghci
ghci> :t read
read :: Read a => String -> a
```
> `String` 只是 `[Char]` 的别名,与 `[Char]` 完全等价,可以互换。
现在起将尽量多用 `String`,因为 `String` 更易于书写,可读性也更高。
不难看出 `read` 函数的返回值属于 `Read` 类型类的实例。
但若不使用这个值,将无法辨认出 `read` 函数的返回类型。
由于 Haskell 是静态类型的,必须在编译前(或者在 GHCi 解释前)清楚所有表达式的类型。
要解决这一问题,可以使用类型注解(type annotation)。
```ghci
ghci> :t read
read :: Read a => String -> a
ghci> read "5" :: Int
5
ghci> read "5" :: Float
5.0
ghci> (read "5" :: Float) * 4
20.0
ghci> read "[1, 2, 3, 4]" :: [Int]
[1,2,3,4]
ghci> read "(3, 'a')" :: (Int, Char)
(3,'a')
```
要 Haskell 辨认出 `read` 函数的返回类型,只需提供最少的信息即可。
```ghci
ghci> [read "True", False, True, False]
[True,False,True,False]
```
## 4.5. `Enum` 类型类
`Enum` 类型类的实例都是有连续顺序的,即值是可枚举的。
`Enum` 类型类的主要好处在于可以在区间中使用这些类型。
包括 `()`、`Bool`、`Char`、`Ordering`、`Int`、`Integer`、`Float` 和 `Double`。
每个值都有相应的前驱(predecessor)和后继(successer)。
前驱和后继分别可以通过 `succ` 函数和 `pred` 函数得到。
```ghci
ghci> ['a' .. 'e']
"abcde"
ghci> [LT .. GT]
[LT,EQ,GT]
ghci> [3 .. 5]
[3,4,5]
ghci> succ 'B'
'C'
```
## 4.6. `Bounded` 类型类
`Bounded` 类型类的实例都有一个上限和下限。
上限和下限分别可以通过 `maxBound` 函数和 `minBound` 函数得到。
```ghci
ghci> minBound :: Int
-9223372036854775808
ghci> maxBound :: Char
'\1114111'
ghci> maxBound :: Bool
True
ghci> minBound :: Bool
False
```
`minBound` 和 `maxBound` 两个函数很有趣,类型都是 `(Bounded a) => a`。
可以说,它们都是多态常量(polymorphic constant)。
如果元组中项的类型都属于 `Bounded` 类型类实例,那么该元组也属于 `Bounded` 类型类实例。
```ghci
ghci> maxBound :: (Bool, Int, Char)
(True,9223372036854775807,'\1114111')
```
## 4.7. `Num` 类型类
`Num` 是一个表示数值的类型类,它的实例都具有数的特征。
检查一个数的类型:
```ghci
ghci> :t 20
20 :: Num p => p
```
可以发现所有的数都是多态常量,它可以具有任何 `Num` 类型类实例的特征。
```ghci
ghci> 20 :: Int
20
ghci> 20 :: Integer
20
ghci> 20 :: Float
20.0
ghci> 20 :: Double
20.0
```
检查 `*` 运算符的类型声明:
```ghci
ghci> :t (*)
(*) :: Num a => a -> a -> a
```
可见 `*` 运算符取两个相同类型的数值作为参数,这是一个类型约束。
执行 `(5 :: Int) * (6 :: Integer)` 会导致类型错误,执行 `5 * (6 :: Integer)` 则不会有问题。
只有已经属于 `Show` 类型类和 `Eq` 类型类的实例,才可以成为 `Num` 类型类的实例。
## 4.8. `Floating` 类型类
`Floating` 类型类用于存储浮点数,仅包含 `Float` 和 `Double` 两种浮点类型。
常见的操作 `Floating` 类型类实例的函数有 `sin`、`cos` 和 `sqrt` 等。
## 4.9. `Integeral` 类型类
`Integernal` 是另一个表示数值的类型类。
`Num` 类型类包含了实数和整数在内的所有的数值相关类型,而 `Integernal` 仅包含整数。
`Integernal` 类型类包含 `Int` 和 `Integer` 两种类型。
`fromIntegral` 是一个非常有用的处理数字的函数。
检查 `fromIntegral` 函数的类型声明:
```ghci
ghci> :t fromIntegral
fromIntegral :: (Integral a, Num b) => a -> b
```
可以看出 `fromIntegral` 函数取一个整数作为参数,返回一个更加通用的数值。
```ghci
ghci> :t length
length :: Foldable t => t a -> Int
ghci> fromIntegral (length [1, 2, 3, 4]) + 3.2
7.2
```
## 4.10. 有关类型类的最后总结
类型类的定义是一个抽象的接口。
一个类型可以是多个类型类的实例,一个类型类可以有多个类型作为实例。
例如 `Char` 类型同时是 `Eq` 类型类和 `Ord` 类型类的实例。
有时,成为某个类型类的实例是成为另一个类型类的实例的先决条件(prerequisite)。
例如某类型必须首先成为 `Eq` 类型类的实例后才能成为 `Ord` 类型类的实例。

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