Type Class

Type Class 类似其他语言里的 Interface、Rust 里的 Trait,定义了一组函数,任何属于该 Type Class 的类型都必须实现这些函数。

When to use Type Class?

Open & Closed Abstraction 原则:

  • Open Abstraction (使用 Type Class) 便于拓展;
  • Closed Abstraction (使用 Pattern Matching) 有助于确认已经处理完所有 cases.

类型约束

Type Class 可以扮演 Rust 中 Trait、C++ concept 的角色,用于约束泛型的类型。例如考察 (==) operator 的实现

1
2
(==) :: (Eq a) => a -> a -> Bool
bothPairsEqual :: (Eq a, Eq b) => a -> a -> b -> b -> Bool

这里的 (Eq a) => 就对泛型类型 a 作出了约束,要求类型 a 必须属于 Eq,即可以比较相等。

标准库里的 Type Class

  • Eq
  • Ord
    • compare :: (Ord a) => a -> a -> Ordering 这里的 Ordering 是个枚举,EQ/LT/GT 分别对应等于 (equal)、小于 (less than)、大于 (greater than).
  • Num, Fractional, Integral, Floating
    • Num: 包含整数加减法、乘法,以及构造函数 fromInteger :: Num a => Integer -> a
    • Integral: 包含整除和取模
    • Fractional: 分数类型
    • Num > Fractional > Floating, Num > Integral
  • Read, Show
    • Show 主要表示“可以把该类型从 a 类型变成字符串”:show :: Show a => a -> String
    • Read 则相反,表示可以从字符串读取其值:read :: Read a => String -> a
  • Foldable
    • foldr 函数真正的签名为 foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
    • Maybe a 的类型也可以用 foldr 函数

自定义 Type Class

我们用 class ... where 语法,很接近 Rust 的 trait Trait {} 语法。我们也可以为其添加默认实现

1
2
3
class Size a where
size :: a -> Int
size x = 1

模板特化:自定义类型实现 Type Class

1
2
instance TypeClass Type where
-- 定义函数 ...

上面这个例子表明类型 Type 实现了 Type Class TypeClass,类似 Rust 中的 impl TypeClass for Type {}.

deriving Ord

对于 deriving Ord 的结构体,其比较顺序为:

  1. 先是构造函数,从左往右递增
  2. 对于同一个构造函数,按参数的先后顺序进行比较

Class Hierarchy

Type Class 之间也可以继承。类似

1
2
class Eq a => Ord a where
...

在定义的时候,给类型参数加上其他的 Type Class 约束即可。


Algebraic Data Type

枚举

1
data Bool = True | False

结构体

1
2
data Point = ConstructPoint Float Float
-- data <Name> = <Constrcutor> ...

这里的 Point 类型有一个构造函数,这个构造函数接受两个 Float 参数。

结构体:Record Syntax

让结构体内部可以起变量名了。

1
2
data Person = MkPerson { name :: String, age :: Int, town :: String, state :: String, profession :: String}
deriving Show

创建变量时,可以指定成员名:

1
MkPerson {name = "Jane Doe", town = "Houston", profession = "Engineer", state = "Texas", age = 21}

最重要的,我们可以用函数调用的语法获取其成员变量:

1
2
-- 访问成员 = 函数:var.state -> state var
profession (MkPerson "Jane Doe" 21 "Houston" "Texas" "Engineer")

枚举与结构体结合

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
2
data Card = Joker | Heart Int | Club Int | Spade Int | Diamond Int
deriving Show

each datatype can be a sum of constructors, and each constructor is a product of fields

How do ADT work?

由于 ADT 是不可变的,当我们希望作出修改时,其本质是 path copying:只有那些被修改的部分产生了复制,而没有修改的部分则共享了底层数据。

所以如果你手写一棵线段树,然后进行修改的话,Haskell 相当于帮你写了一棵可持久化线段树(


类型参数

类似 C++/Rust 里的泛型,例如

1
data Maybe a = Nothing | Just a

这里的 a 就是类型参数。


Recursive Types

有点像链表定义那样

1
2
data IntList = Empty | Node Int IntList
deriving Show