Skip to content
Go back

半群和幺半群

Updated:

半群和幺半群

半群

Haskell 区别于传统软件设计的另一个重要方式是可组合性的理念。可组合性意味着您可以通过组合两个相似的事物来创建新的事物。

组合函数。一个特殊的高阶函数— compose,它采用两个函数作为参数。使用函数组合对于以可读的方式动态组合函数特别有帮助。以下是一些可以使用函数组合轻松表达的函数示例。

myLast :: [a] -> a
myLast = head . reverse
myMin :: Ord a => [a] -> a
myMin = head . sort
myMax :: Ord a => [a] -> a
myMax = myLast . sort

为了进一步探索可组合性,我们看一个非常简单的类型类,称为 Semigroup。为此需要在文件顶部导入 Data.Semigroup

Semigroup 类只有一个重要方法,即 <> 运算符。您可以将 <> 视为组合相同类型实例的运算符。您可以通过将 <> 定义为 + 来简单地实现 Integer 的 Semigroup。

instance Semigroup Integer where
(<>) x y = x + y

(<>) :: Semigroup a => a -> a -> a

这个简单的签名是可组合性思想的核心;你可以把两个相似的东西组合起来,得到一个相同类型的新东西

实例:

data Color = Red |
Yellow |
Blue |
Green |
Purple |
Orange |
Brown deriving (Show,Eq)

instance Semigroup Color where
(<>) Red Blue = Purple
(<>) Blue Red = Purple
(<>) Yellow Blue = Green
(<>) Blue Yellow = Green
(<>) Yellow Red = Orange
(<>) Red Yellow = Orange
(<>) a b = if a == b
then a
else Brown

当添加两种以上的颜色时会遇到一个有趣的问题。b本意希望颜色混合具有关联性。关联意味着应用 <> 运算符的顺序并不重要。对于数字,这意味着 1 + (2 + 3) = (1 + 2) + 3。如您所见,但目前颜色显然不具有关联性:

GHCi> (Green <> Blue) <> Yellow Brown GHCi> Green <> (Blue <> Yellow) Green

这条关于关联性的规则不仅具有直观意义(以任何顺序混合颜色应该给出相同的颜色),而且这是 Semigroup 类型类的正式要求

重新实现:

instance Semigroup Color where
(<>) Red Blue = Purple
(<>) Blue Red = Purple
(<>) Yellow Blue = Green
(<>) Blue Yellow = Green
(<>) Yellow Red = Orange
(<>) Red Yellow = Orange
(<>) a b | a == b = a
| all (`elem` [Red,Blue,Purple]) [a,b] = Purple
| all (`elem` [Blue,Yellow,Green]) [a,b] = Green
| all (`elem` [Red,Yellow,Orange]) [a,b] = Orange
| otherwise = Brown

具有恒等性的组合:幺半群

定义

与 Semigroup 类似的另一个类型类是 Monoid 。 Semigroup 和 Monoid 之间的唯一主要区别是 Monoid 需要类型的单位元素。单位元素意味着 x <> id = x(还有 id <> x = x)。因此,对于整数相加,单位元素将为 0。但在当前状态下,您的 Color 类型没有单位元素。拥有标识元素可能看起来只是一个小细节,但它允许您使用fold函数轻松组合相同类型的列表,从而极大地增强了类型的功能。

class Semigroup a => Monoid a where
identity :: a

Monoid 应该是 Semigroup 的子类,因为它只是具有identity的 Semigroup。但 Monoid 早于 Semigroup,并且并不是 Semigroup 的正式子类。相反,Monoid 的定义令人困惑。

class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a

为什么是mempty的而不是identity?为什么用mappend而不是<>?出现这些奇怪的命名是因为 Monoid 类型类是在 Semigroup 之前添加到 Haskell 中的。最常见的 Monoid 是列表。空列表是列表的identity,++(追加运算符)是列表的 <> 运算符。 Monoid 方法的奇怪名称只是 m(代表 Monoid)附加到常见列表函数上:empty、append 和 concat。在这里可以比较对列表执行相同identity操作的这三种方法:

GHCi> [1,2,3] ++ []
[1,2,3]
GHCi> [1,2,3] <> []
[1,2,3]
GHCi> [1,2,3] `mappend` mempty
[1,2,3]

mconcat:一次组合多个 Monoid

要了解恒等式的强大功能,最简单的方法是探索 Monoid 定义中的最后一个方法:mconcat。 Monoid 中唯一需要的定义是 mempty 和 mappend。

根据mconcat的类型定义 mconcat :: Monoid a => [a] -> a, mconcat的方法作用是获取 Monoid 列表并将它们组合起来,返回一个 Monoid。

实际定义 mconcat = foldr mappend mempty

举例:

GHCi> mconcat [“does”,” this”,” make”,” sense?”] “does this make sense?”

幺半群定律

就像 Semigroup 一样,也有 Monoid 类型的类法则。有四种:

 第一个是mappend mempty x = x。请记住,mappend 与 (++) 相同,而 列表中mempty 是 [],这直观地意味着

[] ++ [1,2,3] = [1,2,3]

第二个只是第一个,顺序相反:mappend x mempty = x。在列表形式中,这是

[1,2,3]++[]=[1,2,3]

 第三个是mappend x (mappend y z) = mappend (mappend x y) z。这只是关联性,对于列表来说:

[1] ++ ([2] ++ [3]) = ([1] ++ [2]) ++ [3]

因为这是半群定律,所以如果 mappend 已经实现为 <>,则可以假定该定律,因为它是半群定律所要求的。

第四个就是我们对mconcat的定义:

mconcat = foldr mappend mempty

mconcat 使用foldr 而不是foldl 的原因是foldr 可以处理无限列表,而foldl 会强制计算。


Suggest Changes

Previous Post
applicative
Next Post
暗黑模式适配