Learn You a Haskell for Great Good 读书笔记 12

Monoids

New Type 将一个已有类型装入一个新的类型

之前我们学过一个ZipList,它可以把一个List的运算按序号应用到另一个List的值上,从而避免List 作为Applicative 的<*>运算直接产生笛卡儿积的情况。

1
2
data ZipList a = ZipList [a]
data ZipList a = ZipList {getZipList :: [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
2
3
4
5
Class Monoid m where
mempty :: m
mappend :: m -> m -> m
mconcat :: [m] -> m
mconcat = foldr mappend mempty

Monoid定律

1
2
3
mempty `mappend` x = x
x `mappend` mempty = x
(x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)

List 作为 Monoid

1
2
3
instance Monoid [a] where 
mempty = []
mappend = (++)

Product 和 Sum 作为 Monoid

1
2
3
4
5
newType Product a = Product {getProduct :: a}
deriving (Eq, Ord, Read, Show, Bounded)
instance Num a => monoid (Product a) where
mempty = Product 1
Product x `mappend` Product y = Product x * y

Any / All 作为 Monoid

1
2
3
4
5
newtype Any = Any {getAny :: Bool}
deriving (Eq, Ord, Read, Show, Bounded)
instance Monoid Any where
mempty = Any False
Any x `mappend` Any y = Any $ x || y

Ordering 作为 Monoid

1
2
3
4
5
instance Monoid Ordering where
mempty = EQ
LT `mappend` _ = LT
EQ `mappend` y = y
GT `mappend` _ = GT

想象一下一个字符串的大小比较,从头到尾一个字符一个字符的比,就能理解这个Monoid 为什么这么定义了。

Maybe 作为 Monoid

1
2
3
4
5
instance Monoid a => Monoid (Maybe a) where
mempty = Nothing
Nothing `mappend` m = m
m `mappend` Nothing = m
Just m1 `append` Just m2 = Just (m1 `mappend` m2)

这就比较像Functor了,Maybe给一种Monoid包了一层,但本身也可以作为Monoid 使用。

如果Maybe的类型参数不是一个Monoid?

因为不是Monoid所以不能叠加,这样就衍生出First 和 Last 两个Monoid

1
2
3
4
5
6
newtype First a = First { getFirst :: Maybe a}
deriving (Eq, Ord, Read, show)
instance Monoid (First a) where
mempty = First Nothing
First (Just x) `mappend` _ = Fisrt Just x
First Nothing `mappend` x = x

第一次出现的值会一直覆盖后面的值,使得monoid三个定律成立

Last 定义类似。

Monoids 与 Foldable

就像我们用Functor 之于了可以被map的数据结构,Monoids的Foldable(最基本的就是[],也可以是自己定义的Tree等)是可以被fold的数据结构

我们在第七节定义了一个Foldable的数据类型Tree:

1
2
3
4
5
6
7
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (show)
foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m
instance Foldable.Foldable Tree where
foldMap f EmptyTree = mempty
foldMap f (Nod x l r) = Foldable.foldMap f l `mappend`
f x `mappend`
Foldable.foldMap f r

这里就用到Monoid结合方法来实现Foldable,大体就是这样。Monoid的数据结构都可以定义实现一个Foldable的fold。