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
newtypeFirst
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)