`Data.Monoid`中所有这些新类型包装的实际价值是什么? [英] What's the practical value of all those newtype wrappers in `Data.Monoid`?

查看:157
本文介绍了`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# - }

  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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆