「Learn Haskell」#6 半群与幺半群
Semigroup
半群(semigroup)是一个集合$S$,它需要指定一个二元运算符$\oplus$,并且满足
$$
a\oplus b \in S\quad a, b\in S
$$
以及结合(associative)律:
$$
(a\oplus b)\oplus c = a\oplus (b\oplus c)
$$
这个二元运算符在Haskell的Semigroup中被定义为<>
函数:
1 | class Semigroup a where |
除此之外还有sconcat
和stimes
函数,都给出了默认实现。对于列表,<>相当于(++),stimes相当于concat . replicate:
1 | ghci> [1, 2] <> [3, 4] |
Semigroup Law
- (x <> y) <> z
=
x <> (y <> z)
补:NonEmpty
NonEmpty表示非空列表,定义是:
1 | data NonEmpty a = a :| [a] deriving (Eq, Ord) |
使用一个元素和一个列表用:|
连接就可以生成一个NonEmpty类型的列表
Data.List.NonEmpty
模块中实现了很多普通列表有的函数,需要qualified import后调用,使用fromList、toList函数可以在普通列表和非空列表之间转换
1 | ghci> import qualified Data.List.NonEmpty as NE |
Monoid
幺半群(Monoid)是一个有单位元素$e$的半群,即$e$满足:
$$
e\oplus x = x\oplus e = x
$$
1 | class Semigroup a => Monoid a where |
可以看出Monoid要求了三个函数,其中最少只需要mempty
,它直接返回一个值,表示单位元素。mappend
即Semigroup中的<>运算符,mconcat
也提供了默认实现
实例
[a]
因为Monoid的实例是一个具体类型,而不是像Functor等一样等类型构造器,所以[]并不是Monoid的实例,但是具体类型[a]是一个幺半群:
1 | instance Semigroup [a] where |
列表的单位元素(mempty)就是空列表[],运算符就是合并列表(++),mconcat也用列表推导重新实现提高效率
1 | ghci> mempty :: [Int] |
Ordering
1 | instance Semigroup Ordering where |
主要可以用于比较字典序:
1 | ghci> mconcat (zipWith compare "abcd" "acbd") |
Sum & Product
对于数字,加法和乘法都满足结合律,所以对于Num,有两种实现Monoid的方式,但是不能为同一类型设置两种实例方式,所以Data.Monoid
中提供了两个包装器————Sum和Product:
1 | newtype Sum a = Sum {getSum :: a} deriving (...) |
它们使用Sum或Product来包装起一个数字,可以通过getSum或getProduct来获取其中的值
对于加法,二元操作为(+),单位元素为0;对于乘法,二元操作为(*),单位元素为1:
1 | instance Num a => Semigroup (Sum a) where |
1 | ghci> Sum 5 <> Sum 6 <> Sum 10 |
All & Any
和数字一样,布尔值也有两种实现Monoid的方式,因此Data.Monoid
模块中也提供了两个包装器,分别实现了这两种Monoid:
1 | newtype All = All { getAll :: Bool } deriving (...) |
1 | ghci> getAll (All True <> mempty <> All False) |
Monoid a => Maybe a
如果a是一个(幺)半群,那么Maybe a也是一个幺半群,单位元就是Nothing:
1 | instance Semigroup a => Semigroup (Maybe a) where |
1 | ghci> Nothing <> Just "andy" |
First & Last
对于Maybe也有两种实现Monoid的方法,即<>操作每次恒取左边和每次恒取右边(在没有Nothing的情况下),所以Data.Monoid
模块中也提供了两个新的包装器:First和Last:
1 | newtype First a = First { getFirst :: Maybe a } deriving (...) |
1 | ghci> getFirst (First (Just "hello") <> First Nothing <> First (Just "world")) |
Min & Max
对于有界的类型,也有两种实现Monoid的方式,每次二元操作都取最小或最大。Data.Semigroup
模块中提供了两个包装其器:Min和Max:
1 | newtype Min a = Min { getMin :: a } deriving (...) |
1 | ghci> Min 3 <> Min 5 |
元组
当元组内的所有元素都是幺半群时,整个元组也是一个幺半群:
1 | instance (Semigroup a, Semigroup b) => Semigroup (a, b) where |
1 | ghci> mconcat $ map (\x -> (Min x, Max x)) [1..10] :: (Min Int, Max Int) |
Monoid Laws
- mempty <> x
=
x - x <> mempty
=
x - (x <> y) <> z
=
x <> (y <> z)
Monoidal classes
Applicative、Monad、Arrow都有有幺半群性质的子类型类,分别是Alternative、MonadPlus、ArrowPlus
Alternative
1 | class Applicative f => Alternative f where |
其中empty是幺半群中的单位元素,<|>是幺半群中的二元运算符。some和many是两个函数(意义还不懂)
Alternative实例
[]
1 | instance Alternative [] where |
和Monoid一样,单位元素是空列表,二元运算是列表合并
1 | ghci> [1,2,3] <|> empty <|> [4,5] |
Maybe
1 | instance Alternative Maybe where |
Maybe作为Alternative的单位元素是Nothing,二元运算是始终取左边(当左边不为Nothing时)
1 | ghci> Nothing <|> Just 1 <|> Just 2 |
ZipList
1 | instance Alternative ZipList where |
1 | <>getZipList $ ZipList [1,2] <|> ZipList [3,4,5,6] |
Alternative Laws
Monoid laws
:1
2
3empty <|> x = x
x <|> empty = x
(x <|> y) <|> z = x <|> (y <|> z)Left zero law
:empty <*> f=
empty
以上的定律是都满足都,下面的定律只有部分满足:Right zero law
:f <*> empty=
empty (大部分包括Maybe、[]满足,IO不满足)Left distribution
:(a <|> b) <*> c=
(a <*> c) <|> (b <*> c) (Maybe、[]满足,IO及大部分parsers不满足)Right distribution
:a <*> (b <|> c)=
(a <*> b) <|> (a <*> c) (大部分不满足,但Maybe满足)Left catch
:(pure a) <|> x = pure a (Maybe、IO、parsers满足,但[]不满足)
常用函数
asum
:: (Foldable t, Alternative f) => t (f a) -> f a,相当于foldr (<|>) empty:1
2
3
4ghci> asum [Nothing, Just 5, Just 3]
Just 5
ghci> asum [[2],[3],[4,5]]
[2,3,4,5]guard
:: (Alternative f) => Bool -> f ():1
2guard True = pure ()
guard False = empty
MonadPlus
1 | class (Alternative m, Monad m) => MonadPlus m where |
MonadPlus实例
[]、Maybe都是MonadPlus的实例,mzero和mplus都由Alternative实现
MonadPlus Laws
Monoid laws
Left zero
:mzero >>= f=
mzeroRight zero
:m >> mzero=
mzero
常用函数
msum
= asummfilter
:1
2
3mfilter p ma = do
a <- ma
if p a then return a else mzero
ArrowPlus
ArrowZero和ArrowPlus分别为Arrow设置了Monoid中的单位元素和二元运算符,使之成为了一个幺半群:
1 | class Arrow arr => ArrowZero arr where |
Reference
- Typeclassopedia - Haskell wiki
- Haskell语言学习笔记(8)Monoid - zwvista
- Haskell语言学习笔记(16)Alternative - zwvista
目录
#0 | 总章
#1 | 基础语法与函数
#2 | 高阶函数与模块
#3 | 类型与类型类
#4 | 输入输出与文件
#5 | 函子、应用函子与单子
#6 | 半群与幺半群
#7 | 一些其它类型类
#A | Haskell与范畴论
「Learn Haskell」#6 半群与幺半群