Monoids
New Type 将一个已有类型装入一个新的类型
之前我们学过一个ZipList,它可以把一个List的运算按序号应用到另一个List的值上,从而避免List 作为Applicative 的<*>运算直接产生笛卡儿积的情况。
1 | data ZipList a = ZipList [a] |
我们还可以使用newtype这个关键字去定义ZipList
1 | newtype ZipList a = ZipList {getZipList :: [a]} |
好处是比data在后面运行速度上更快,因为newtype告诉Haskell你定义的新类型它是从已有类型而来,不会有类型的拆装动作。但它的局限是:
- 只能有一个值构造器value constructor
- 值构造器只能有一个类型参数
就如ZipList的定义。
type vs. newtype vs. data
- type 只是给已存在的类型一个重命名,好看一点
- newtype 是将已有类型包装成一个全新的类型,使其被实例化时能更方便
- data 给你组装你自己的数据结构
Monoid
- 一个函数接受两个参数
- 函数返回值与入参同类型
- 存在一个值使返回值和另一个参数相同 (
f x = id) - 这个函数是Associative的,就是括号不影响结果
则这个函数以及第三点那个值组成了一个Moniod类型:
1 | Class Monoid m where |
Monoid定律
1 | mempty `mappend` x = x |
List 作为 Monoid
1 | instance Monoid [a] where |
Product 和 Sum 作为 Monoid
1 | newType Product a = Product {getProduct :: a} |
Any / All 作为 Monoid
1 | newtype Any = Any {getAny :: Bool} |
Ordering 作为 Monoid
1 | instance Monoid Ordering where |
想象一下一个字符串的大小比较,从头到尾一个字符一个字符的比,就能理解这个Monoid 为什么这么定义了。
Maybe 作为 Monoid
1 | instance Monoid a => Monoid (Maybe a) where |
这就比较像Functor了,Maybe给一种Monoid包了一层,但本身也可以作为Monoid 使用。
如果Maybe的类型参数不是一个Monoid?
因为不是Monoid所以不能叠加,这样就衍生出First 和 Last 两个Monoid
1 | newtype First a = First { getFirst :: Maybe a} |
第一次出现的值会一直覆盖后面的值,使得monoid三个定律成立
Last 定义类似。
Monoids 与 Foldable
就像我们用Functor 之于了可以被map的数据结构,Monoids的Foldable(最基本的就是[],也可以是自己定义的Tree等)是可以被fold的数据结构
我们在第七节定义了一个Foldable的数据类型Tree:
1 | data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (show) |
这里就用到Monoid结合方法来实现Foldable,大体就是这样。Monoid的数据结构都可以定义实现一个Foldable的fold。