Monoid newtypes:一个零空间无操作告诉编译器该做什么
Monoids 非常适合将现有数据类型包装在新类型中,以告诉编译器您要执行什么操作。
由于它们是新类型,因此它们不会占用任何额外的空间并且应用Sum
或者getSum
是空操作。
示例:Foldable 中的 Monoids
概括 foldr 的方法不止一种(对于最一般的折叠,请参阅这个非常好的问题,如果您喜欢下面的树示例但想查看最一般的树折叠,请参阅这个问题)。
一种有用的方法(不是最通用的方法,但绝对有用)是说某些东西是可折叠的,如果您可以将其元素与二进制操作和开始/身份元素组合成一个。这就是类型类的重点Foldable
。
无需显式传入二进制操作和起始元素,Foldable
只需询问元素数据类型是 Monoid 的实例。
乍一看,这似乎令人沮丧,因为我们只能对每种数据类型使用一个二元运算——但我们应该使用(+)
and 0
forInt
和求和而不是乘积,还是反过来呢?当我们想要其他操作时,也许我们应该使用((+),0)
forInt
和(*),1
forInteger
和 convert ?这不会浪费很多宝贵的处理器周期吗?
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
如果您想映射和折叠您自己的 ADT 而无需自己编写繁琐的实例,则 和 扩展 ( )非常DeriveFunctor
有用DeriveFoldable
。{-# LANGUAGE DeriveFunctor, DeriveFoldable #-}
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 " []]]
示例用法
(++)
使用and字符串已经是一个幺半群[]
,所以我们可以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
(and 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
结论
我们可以使用 newtype Monoid 包装器来告诉编译器以哪种方式将事物成对组合。
标签什么都不做,但显示要使用的组合功能。
这就像将函数作为隐式参数而不是显式参数传递(因为这就是类型类所做的事情)。