Type Class
Type Class 类似其他语言里的 Interface、Rust 里的 Trait,定义了一组函数,任何属于该 Type Class 的类型都必须实现这些函数。
Open & Closed Abstraction 原则:
- Open Abstraction (使用 Type Class) 便于拓展;
- Closed Abstraction (使用 Pattern Matching) 有助于确认已经处理完所有 cases.
类型约束
Type Class 可以扮演 Rust 中 Trait、C++ concept 的角色,用于约束泛型的类型。例如考察 (==) operator 的实现
1 | (==) :: (Eq a) => a -> a -> Bool |
这里的 (Eq a) => 就对泛型类型 a 作出了约束,要求类型 a 必须属于 Eq,即可以比较相等。
标准库里的 Type Class
EqOrdcompare :: (Ord a) => a -> a -> Ordering这里的Ordering是个枚举,EQ/LT/GT分别对应等于 (equal)、小于 (less than)、大于 (greater than).
Num, Fractional, Integral, FloatingNum: 包含整数加减法、乘法,以及构造函数fromInteger :: Num a => Integer -> aIntegral: 包含整除和取模Fractional: 分数类型Num > Fractional > Floating, Num > Integral
Read, ShowShow主要表示“可以把该类型从a类型变成字符串”:show :: Show a => a -> StringRead则相反,表示可以从字符串读取其值:read :: Read a => String -> a
Foldablefoldr函数真正的签名为foldr :: Foldable t => (a -> b -> b) -> b -> t a -> bMaybe a的类型也可以用foldr函数
自定义 Type Class
我们用 class ... where 语法,很接近 Rust 的 trait Trait {} 语法。我们也可以为其添加默认实现
1 | class Size a where |
模板特化:自定义类型实现 Type Class
1 | instance TypeClass Type where |
上面这个例子表明类型 Type 实现了 Type Class TypeClass,类似 Rust 中的 impl TypeClass for Type {}.
deriving Ord对于 deriving Ord 的结构体,其比较顺序为:
- 先是构造函数,从左往右递增
- 对于同一个构造函数,按参数的先后顺序进行比较
Type Class 之间也可以继承。类似
1 | class Eq a => Ord a where |
在定义的时候,给类型参数加上其他的 Type Class 约束即可。
Algebraic Data Type
枚举
1 | data Bool = True | False |
结构体
1 | data Point = ConstructPoint Float Float |
这里的 Point 类型有一个构造函数,这个构造函数接受两个 Float 参数。
结构体:Record Syntax
让结构体内部可以起变量名了。
1 | data Person = MkPerson { name :: String, age :: Int, town :: String, state :: String, profession :: String} |
创建变量时,可以指定成员名:
1 | MkPerson {name = "Jane Doe", town = "Houston", profession = "Engineer", state = "Texas", age = 21} |
最重要的,我们可以用函数调用的语法获取其成员变量:
1 | -- 访问成员 = 函数:var.state -> state var |
枚举与结构体结合
1 | data Shape = Circle Float | Rectangle Int Int |
deriving 自动结合 Type Class
The deriving syntax is a way to automatically make your class a member of certain basic type classes, most notably Read, Show and Eq.
1 | data Card = Joker | Heart Int | Club Int | Spade Int | Diamond Int |
each datatype can be a sum of constructors, and each constructor is a product of fields
由于 ADT 是不可变的,当我们希望作出修改时,其本质是 path copying:只有那些被修改的部分产生了复制,而没有修改的部分则共享了底层数据。
所以如果你手写一棵线段树,然后进行修改的话,Haskell 相当于帮你写了一棵可持久化线段树(
类型参数
类似 C++/Rust 里的泛型,例如
1 | data Maybe a = Nothing | Just a |
这里的 a 就是类型参数。
Recursive Types
有点像链表定义那样
1 | data IntList = Empty | Node Int IntList |