Haskell 什么';“Data.Monoid”中所有这些新类型包装的实用价值是什么?

Haskell 什么';“Data.Monoid”中所有这些新类型包装的实用价值是什么?,haskell,monoids,Haskell,Monoids,当查看Data.Monoid时,我看到有各种newtype包装器,例如All、Sum或Product,它们对各种类型的Monoid进行编码。然而,当尝试使用这些包装器时,我不禁想知道使用它们的非Data.Monoid对应项有什么好处。例如,比较一下相当繁琐的总和 print $ getSum $ mconcat [ Sum 33, Sum 2, Sum 55 ] 与更简洁的惯用变体相比 print $ sum [ 33, 2, 55 ] 但这有什么意义呢?所有这些newtypewrapper

当查看
Data.Monoid
时,我看到有各种
newtype
包装器,例如
All
Sum
Product
,它们对各种类型的Monoid进行编码。然而,当尝试使用这些包装器时,我不禁想知道使用它们的非
Data.Monoid
对应项有什么好处。例如,比较一下相当繁琐的总和

print $ getSum $ mconcat [ Sum 33, Sum 2, Sum 55 ]
与更简洁的惯用变体相比

print $ sum [ 33, 2, 55 ]

但这有什么意义呢?所有这些
newtype
wrapper有什么实用价值吗?与上面的例子相比,
Monoid
newtype
包装器用法是否有更令人信服的例子?

我认为,基本思想是

reduce = foldl (<>) mempty
reduce=foldl()内存

它适用于那些包装好的东西的任何列表。

假设您在
编写器
单子中工作,并且希望存储您
告诉的所有内容的总和。在这种情况下,您需要
newtype
包装器

您还需要使用
newtype
来使用像
foldMap
这样具有
Monoid
约束的函数

lens
软件包中的
ala
alaf
组合器可以使使用这些新类型更加愉快。从文件中:

>>> alaf Sum foldMap length ["hello","world"]
10

>>> ala Sum foldMap [1,2,3,4]
10

在这样的情况下如何:

myData :: [(Sum Integer, Product Double)]
myData = zip (map Sum [1..100]) (map Product [0.01,0.02..])

main = print $ mconcat myData
或者不使用newtype包装器和
Monoid
实例:

myData :: [(Integer, Double)]
myData = zip [1..100] [0.01,0.02..]

main = print $ foldr (\(i, d) (accI, accD) -> (i + accI, d * accD)) (0, 1) myData
这是因为
(幺半群a,幺半群b)=>幺半群(a,b)
。现在,如果您有自定义数据类型,并且希望应用二进制操作折叠这些值的元组,该怎么办?您可以简单地编写一个新类型包装器,并使用该操作将其作为
Monoid
的实例,构造元组列表,然后使用
mconcat
折叠它们。还有许多其他函数也适用于
Monoid
s,而不仅仅是
mconcat
,因此肯定有无数的应用程序


您还可以查看
第一个
最后一个
新类型包装器,我可以想到它们的许多用途。如果你需要编写很多函数,
Endo
包装器很不错,
Any
All
包装器很适合处理布尔函数。

Monoid newtypes:零空间无操作告诉编译器该做什么 Monoids非常适合将现有的数据类型包装成一个新类型,以告诉编译器您要执行的操作

因为它们是新类型,所以不占用任何额外的空间,应用
Sum
getSum
是不可行的

示例:可折叠模型中的幺半群 概括foldr的方法不止一种(请参阅最通用的折叠,如果您喜欢下面的树示例,但希望看到最通用的树折叠)

一种有用的方法(不是最通用的方法,但绝对有用)是说,如果可以将某个元素与二进制操作和start/identity元素组合成一个元素,那么就可以说它是可折叠的。这就是
Foldable
typeclass的要点

与其显式地传递二进制操作并启动元素,
Foldable
只要求元素数据类型是Monoid的实例

乍一看,这似乎令人沮丧,因为我们只能对每个数据类型使用一个二进制操作-但是我们应该对
Int
使用
(+)
0
,并且取和而不取乘积,还是相反?也许我们应该使用
(+)、0
表示
Int
(*)、1
表示
整数
并在需要其他操作时进行转换?这不会浪费很多宝贵的处理器周期吗

拯救幺半群 如果我们想加法,我们需要做的就是用
Sum
标记;如果我们想乘法,我们需要用
Product
标记;如果我们想做一些不同的事情,我们甚至需要用手卷起的newtype标记

让我们折几棵树吧!我们需要

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
扩展(
{-#语言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 " []]]
示例用法 字符串已经是使用
(++)
[]
的幺半群,因此我们可以用它们来
折叠
,但数字不是,所以我们将使用
折叠映射
来标记它们

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包装器来告诉编译器以哪种方式成对组合对象

标记不起任何作用,但显示要使用的组合函数


这就像是将函数作为隐式参数而不是显式参数传递进来(因为类型类无论如何都是这样做的)。

有时您只需要一个特定的
幺半群来填充类型约束。有时出现的一个地方是
Const
有一个
Applicative
实例,如果它存储了
Monoid

instance Monoid m => Applicative (Const m) where
  pure _ = Const mempty
  Const a <*> Const b = Const (a <> b)
如果使用
Monoid
newtype
First

newtype First a = First { getFirst :: Maybe a }

-- Retains the first, leftmost 'Just'
instance Monoid (First a) where
  mempty = First Nothing
  mappend (First Nothing)  (First Nothing) = First Nothing
  mappend (First (Just x)) _               = First (Just x)
然后我们可以解释这种类型

(a -> Const (First a) a) -> (s -> Const (First a) s)
扫描
s
并拾取其中的第一个
a



因此,虽然这是一个非常具体的答案,但广泛的回答是,有时能够讨论一系列不同的默认
Monoid
行为是有用的。无论如何,必须有人编写所有明显的
Monoid
行为,它们也可能被放入
数据中。Monoid

我不能说我曾经使用过它们。
sum=getSum。Data.Foldable.foldMap Sum
不是
reduce
只是
mconcat
foldl
实现,而不是
foldr
?顺便说一句,第一个示例的输出与2nd@danom你是s吗
instance Monoid m => Applicative (Const m) where
  pure _ = Const mempty
  Const a <*> Const b = Const (a <> b)
newtype First a = First { getFirst :: Maybe a }

-- Retains the first, leftmost 'Just'
instance Monoid (First a) where
  mempty = First Nothing
  mappend (First Nothing)  (First Nothing) = First Nothing
  mappend (First (Just x)) _               = First (Just x)
(a -> Const (First a) a) -> (s -> Const (First a) s)