Monoid newtypes:一个零空间无操作告诉编译器做什么
Monoids 非常适合将现有数据类型包装在新类型中,以告诉编译器您要执行什么操作。
由于它们是新类型,它们不会占用任何额外空间,并且应用 Sum 或 getSum 是无操作的。
示例:Foldable 中的 Monoids
泛化 foldr 的方法不止一种(请参阅 this very good question 了解最通用的折叠,如果您喜欢下面的树示例但想查看最通用的树折叠,请参阅 this question)。
一种有用的方法(不是最通用的方法,但绝对有用)是说某些东西是可折叠的,如果你可以将它的元素与二进制操作和开始/标识元素组合成一个。这就是Foldable 类型类的重点。
Foldable 只是询问元素数据类型是 Monoid 的实例,而不是显式传递二进制操作和起始元素。
乍一看,这似乎令人沮丧,因为我们只能对每种数据类型使用一个二元运算 - 但我们应该使用 (+) 和 0 来表示 Int 并求和但从不求积,还是反过来?也许我们应该将((+),0) 用于Int 和(*),1 用于Integer 并在我们需要其他操作时进行转换?这不会浪费很多宝贵的处理器周期吗?
Monoids 救援
如果我们想要添加,我们需要做的只是标记Sum,如果我们想要乘以标记Product,或者如果我们想要做一些不同的事情,甚至可以使用手动新类型标记。
让我们折几棵树吧!我们需要
fold :: (Foldable t, Monoid m) => t m -> m
-- if the element type is already a monoid
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
-- if you need to map a function onto the elements first
DeriveFunctor 和 DeriveFoldable 扩展 ({-# LANGUAGE DeriveFunctor, DeriveFoldable #-}) 非常棒,如果您想映射和折叠自己的 ADT 而无需自己编写繁琐的实例。
import Data.Monoid
import Data.Foldable
import Data.Tree
import Data.Tree.Pretty -- from the pretty-tree package
see :: Show a => Tree a -> IO ()
see = putStrLn.drawVerticalTree.fmap show
numTree :: Num a => Tree a
numTree = Node 3 [Node 2 [],Node 5 [Node 2 [],Node 1 []],Node 10 []]
familyTree = Node " Grandmama " [Node " Uncle Fester " [Node " Cousin It " []],
Node " Gomez - Morticia " [Node " Wednesday " [],
Node " Pugsley " []]]
示例用法
字符串已经是使用(++) 和[] 的幺半群,所以我们可以使用它们fold,但数字不是,所以我们将使用foldMap 标记它们。
ghci> see familyTree
" Grandmama "
|
----------------------
/ \
" Uncle Fester " " Gomez - Morticia "
| |
" Cousin It " -------------
/ \
" Wednesday " " Pugsley "
ghci> fold familyTree
" Grandmama Uncle Fester Cousin It Gomez - Morticia Wednesday Pugsley "
ghci> see numTree
3
|
--------
/ | \
2 5 10
|
--
/ \
2 1
ghci> getSum $ foldMap Sum numTree
23
ghci> getProduct $ foldMap Product numTree
600
ghci> getAll $ foldMap (All.(<= 10)) numTree
True
ghci> getAny $ foldMap (Any.(> 50)) numTree
False
滚动你自己的 Monoid
但是如果我们想找到最大元素怎么办?我们可以定义自己的幺半群。我不知道为什么Max(和Min)不在。也许是因为没有人喜欢考虑Int 是有界的,或者他们只是不喜欢基于实现细节的标识元素.无论如何,这里是:
newtype Max a = Max {getMax :: a}
instance (Ord a,Bounded a) => Monoid (Max a) where
mempty = Max minBound
mappend (Max a) (Max b) = Max $ if a >= b then a else b
ghci> getMax $ foldMap Max numTree :: Int -- Int to get Bounded instance
10
结论
我们可以使用新类型的 Monoid 包装器来告诉编译器以哪种方式将事物成对组合。
标签什么都不做,只是显示要使用的组合功能。
这就像将函数作为隐式参数而不是显式参数传递(因为无论如何这都是类型类所做的)。