`Data.Monoid`中所有这些新类型包装的实际价值是什么? [英] What's the practical value of all those newtype wrappers in `Data.Monoid`?
问题描述
在查看 Data.Monoid
时,我发现有各种各样的 newtype
包装器,比如 All
, Sum
或 Product
,它们编码各种monoid。然而,当试图使用这些包装时,我不禁想知道使用它们的非 Data.Monoid
对应物有什么好处。例如,比较相当麻烦的总和
pre $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ b
vs。更简洁的惯用变体
print $ sum [33,2,55]
但是有什么意义?有所有这些 newtype
包装的实际价值吗?是否有更令人信服的例子比 Monoid
newtype
封装器的使用情况比上面的要少?
Monoid newtypes:一个零空间无操作告诉编译器该做什么
Monoid很棒以新类型包装现有数据类型,以告知编译器您想要执行的操作。
由于它们是新类型,它们不会占用额外的空间并应用 Sum
或 getSum
是无操作。
示例:可折叠的幺半元
有多种方法可以概括foldr(请参阅这是一个非常好的问题最普遍的折叠,和这个问题,如果你喜欢下面的树例子,但是想看到树的最普遍的折叠)。
一种有用的方法(不是最常用的方式,但绝对有用)是说如果可以将其元素与二元操作和一个开始/标识元素。这就是 Foldable
typeclass的要点。
不是显式地传入二进制操作和start元素,而是 Foldable
只是要求元素数据类型是乍看起来,这似乎令人沮丧,因为我们每个数据类型只能使用一个二进制操作 - 但是我们应该使用(+) 和
0
对于 Int
并且花钱但从来没有产品,或者相反?也许应该为 Int
和(*)使用
用于((+),0)
1 整数
并且当我们需要其他操作时进行转换?这不会浪费大量宝贵的处理器周期吗?
骷髅拯救
我们需要如果我们想要添加标签,使用 Sum
标签,如果我们想要相乘,或者甚至用标签标签,使用 Product
标签如果我们想要做一些不同的事情,那就是手卷新型。
让我们折叠一些树!我们需要
fold ::(Foldable t,Monoid m)=> t m - > m
- 如果元素类型已经是monoid
foldMap ::(可折叠t,Monoid m)=> (a - > m) - > t a - > m
- 如果你需要将一个函数映射到元素上,首先
DeriveFunctor
和 DeriveFoldable
扩展( { - #LANGUAGE DeriveFunctor,DeriveFoldable# - } $ c $
import数据是非常棒的,如果您想映射并折叠自己的ADT而不用自己编写繁琐的实例。 .Monoid
导入Data.Foldable
导入Data.Tree
导入Data.Tree.Pretty - 从漂亮的包
中查看:: Show a = >树a - > IO()
see = putStrLn.drawVerticalTree.fmap show
numTree :: Num a =>树a
numTree =节点3 [节点2 [],节点5 [节点2 [],节点1 []],节点10 []]
familyTree =节点奶奶节点Uncle Fester[节点表哥它[]],
节点戈麦斯 - Morticia[节点星期三[],
节点Pugsley[]]]
示例用法
字符串已经是使用(++)
和 []
,所以我们可以用它们 fold
> ,但数字不是,所以我们使用 foldMap
来标记它们。
ghci的>见familyTree
奶奶
|
----------------------
/ \
Fcle叔叔Gomez - Morticia
| |
Cousin It-------------
/ \
WednesdayPugsley
ghci>折叠familyTree
奶奶叔叔Fester表哥它戈麦斯 - Morticia星期三Pugsley
ghci>请参阅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}
实例(Ord a,Bounded a)=> Monoid(Max a)其中
mempty = Max minBound
mappend(Max a)(Max b)= Max $如果a> = b则a else b
ghci> getMax $ foldMap Max numTree :: Int - Int获取有界实例
10
结论
我们可以使用newtype Monoid包装器来告诉编译器以哪种方式将对象组合在一起。
标签什么都不做,但是要显示使用什么组合函数。
这就像传递一个隐式参数而不是一个明确的参数一样(因为这就是类型类无论如何)。
When looking at Data.Monoid
, I see there are various newtype
wrappers, such as All
, Sum
, or Product
, which encode various kinds of monoids. However, when trying to use those wrappers, I can't help but wonder what's the benefit over using their non-Data.Monoid
counterparts. For instance, compare the rather cumbersome summation
print $ getSum $ mconcat [ Sum 33, Sum 2, Sum 55 ]
vs. the more succinct idiomatic variant
print $ sum [ 33, 2, 55 ]
But what's the point? Is there any practical value having all those newtype
wrappers? Are there more convincing examples of Monoid
newtype
wrapper usage than the one above?
Monoid newtypes: A zero space no-op to tell the compiler what to do
Monoids are great to wrap an existing data type in a new type to tell the compiler what operation you want to do.
Since they're newtypes, they don't take any additional space and applying Sum
or getSum
is a no-op.
Example: Monoids in Foldable
There's more than one way to generalise foldr (see this very good question for the most general fold, and this question if you like the tree examples below but want to see a most general fold for trees).
One useful way (not the most general way, but definitely useful) is to say something's foldable if you can combine its elements into one with a binary operation and a start/identity element. That's the point of the Foldable
typeclass.
Instead of explicitly passing in a binary operation and start element, Foldable
just asks that the element data type is an instance of Monoid.
At first sight this seems frustrating because we can only use one binary operation per data type - but should we use (+)
and 0
for Int
and take sums but never products, or the other way round? Perhaps should we use ((+),0)
for Int
and (*),1
for Integer
and convert when we want the other operation? Wouldn't that waste a lot of precious processor cycles?
Monoids to the rescue
All we need to do is tag with Sum
if we want to add, tag with Product
if we want to multiply, or even tag with a hand-rolled newtype if we want to do something different.
Let's fold some trees! We'll need
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
The DeriveFunctor
and DeriveFoldable
extensions ({-# LANGUAGE DeriveFunctor, DeriveFoldable #-}
) are great if you want to map over and fold up your own ADT without writing the tedious instances yourself.
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 " []]]
Example usage
Strings are already a monoid using (++)
and []
, so we can fold
with them, but numbers aren't, so we'll tag them using 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
Roll your own Monoid
But what if we wanted to find the maximum element? We can define our own monoids. I'm not sure why Max
(and Min
) aren't in. Maybe it's because no-one likes thinking about Int
being bounded or they just don't like an identity element that's based on an implementation detail. In any case here it is:
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
Conclusion
We can use newtype Monoid wrappers to tell the compiler which way to combine things in pairs.
The tags do nothing, but show what combining function to use.
It's like passing the functions in as an implicit parameter rather than an explicit one (because that's kind of what a type class does anyway).
这篇关于`Data.Monoid`中所有这些新类型包装的实际价值是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!