Monoid新类型:零空间无操作,告诉编译器该执行什么操作
Monoid很适合用于在新类型中包装现有数据类型,以告诉编译器你想要进行的操作。
由于它们是新类型,它们不占用任何额外空间,并且应用Sum
或getSum
是一个无操作。
示例:Foldable中的Monoids
泛化foldr有不止一种方法(参见这个非常好的问题了解最通用的折叠方式,以及这个问题如果你喜欢下面的树示例,但想看到树的最通用折叠)。
一种有用的方式(不是最通用的方式,但绝对有用)是说如果你可以使用二元操作和起始/身份元素将其元素组合成一个元素,则可以将某些东西称为可折叠的。这就是Foldable
类型类的重点。
与显式传递二元操作和起始元素不同,Foldable
只要求元素数据类型是Monoid的实例。
乍一看,这似乎令人沮丧,因为我们只能针对每种数据类型使用一个二元操作-但是我们应该使用(+)
和0
进行Int
的求和,但永远不要使用乘法,还是反过来?也许我们应该对于Int
使用((+),0)
,对于Integer
使用(*),1
,并在需要执行另一个操作时进行转换?那样会浪费很多宝贵的处理器周期吗?
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。请注意保留 HTML 标签。
import Data.Monoid
import Data.Foldable
import Data.Tree
import Data.Tree.Pretty
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
但是如果我们想要找到最大的元素呢?我们可以定义自己的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
10
结论
我们可以使用newtype Monoid包装器告诉编译器如何将两个事物组合在一起。
标签本身没有作用,只是展示了要使用哪种组合函数。
这就像将函数隐式地作为参数传入,而不是显式地传入(因为这也是类型类的作用)。
sum = getSum . Data.Foldable.foldMap Sum
- user2407038