我正在阅读在线LYAH一本书(该链接会直接转到我的问题所关注的部分)。
作者定义了二叉树数据类型,并展示了如何通过实现foldMap函数将其作为Foldable类型的实例(在Data.Foldable中定义):
import Data.Monoid
import qualified Data.Foldable as F
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
instance F.Foldable Tree where
foldMap f Empty = mempty
foldMap f (Node x l r) = F.foldMap f l `mappend`
f x `mappend`
F.foldMap f r
foldMap的类型声明如下:
F.foldMap :: (Monoid m, F.Foldable t) => (a -> m) -> t a -> m
所以它需要一个函数,它接受一个类型为“a”的实例并返回一个monoid。
现在作为一个例子,作者创建了一个Tree实例
testTree = Node 5
(Node 3
(Node 1 Empty Empty)
(Node 6 Empty Empty)
)
(Node 9
(Node 8 Empty Empty)
(Node 10 Empty Empty)
)
并执行以下折叠(为可折叠类型定义):
F.foldl (+) 0 testTree -- the answer is 42 (sum of the Node Integers)
我的问题是,Haskell如何计算出Integer类型的添加 - 查询Haskell的testTree类型给出Tree [Integer] - 可以被视为一个monoid操作(如果我的术语是正确的)?
(我自己尝试回答:作者在本节前几段描述了 Num 类型如何以两种不同的方式解释为 Monoid 类型;将它们包装成 Sum 和 Product 类型,其中(+)和(*)作为 mappend 函数,0和1作为 mempty 元素。是否将(树 a)中的“a”类型推断为属于 Sum 类型(Haskell的方式不同)根据上下文解释数值)还是完全不同的其他东西?]
答案 0 :(得分:12)
我的问题是,Haskell如何计算出Integer类型的加法 - 查询Haskell的testTree类型给出Tree [Integer] - 可以看作是一个monoid操作(如果我的术语是正确的)?
它不能!实际上,Monoid
没有Integer
个实例。
现在,不要误解我的意思 - 整数 是一个绰号。然而,它们在乘法中也是一个幺半群,并且Haskell无法知道使用哪个,因此newtype
包装器。
但......这些都不是这里发生的事情。继续......
(我自己尝试回答:作者在本节前几段描述了Num类型如何以两种不同的方式解释为Monoid类型;通过(+)将它们包装到Sum和Product类型中(*)作为mappend函数,0和1分别作为mempty元素。(树a)中的“a”的类型被推断为属于Sum类型(Haskell根据类型不同地解释数值的方式)上下文)还是其他完全不同的东西?]
不是一个糟糕的猜测,但这种推断(根据你给出的参数使用Sum
查找实例)超出了Haskell可以为你做的事情。
这里有两个关键点 - 首先,Monoid
约束仅用于某些功能,而不是一般的折叠。特别是,foldl
实际上根本不需要Monoid
实例,因为您提供二进制操作和初始值供它使用。
第二点是我怀疑你真正追求的 - 当你所定义的是foldl
时,它如何创建一个不需要Monoid
的通用foldMap
,哪个呢?要回答这个问题,我们只需foldl
look at the default implementation {/ 3>}
foldl :: (a -> b -> a) -> a -> t b -> a
foldl f z t = appEndo (getDual (foldMap (Dual . Endo . flip f) t)) z
此处,Endo
is another newtype
wrapper,特别是针对a -> a
合成的Monoid
函数,id
作为标识,而Dual
is a wrapper则反转方向一个Monoid
。所以它实际上使用的Monoid
是这样它可以将(+)
与函数组合一起使用,然后将结果应用于种子值。
答案 1 :(得分:5)
这里实际上没有使用幺半群。最后一行使用F.foldl
,其中包含签名F.Foldable t => (a -> b -> a) -> a -> t b -> a
。基本上你通过提供(+)和0来“手动”使用monoid。
如果您想“隐式”使用monoid,可以使用F.fold
(具有签名(F.Foldable t, Monoid m) -> t m -> m
)。在这种情况下,如果您尝试它,您将获得:
*Main> F.fold testTree
<interactive>:1:1:
No instance for (Monoid Integer)
arising from a use of `F.fold'
Possible fix: add an instance declaration for (Monoid Integer)
In the expression: F.fold testTree
In an equation for `it': it = F.fold testTree
*Main> :t F.foldl
现在,GHCI抱怨说Integer没有Monoid实例。您必须通过包装整数来选择Sum或Product。为此,我们可以使用F.foldMap
(签名(F.Foldable t, Monoid m) => (a -> m) -> t a -> m
):
*Main> F.foldMap Sum testTree
Sum {getSum = 42}