我正在通过Learn You a Haskell工作,我正在讨论幺半群。在本节中,作者为树定义了foldMap方法,如下所示:
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
哪个工作正常,完全是个傻瓜。然而,他然后说“现在我们的树类型有一个可折叠的实例,我们可以免费获得foldr和foldl!”并显示以下代码:
testTree = Node 5
(Node 3
(Node 1 Empty Empty)
(Node 6 Empty Empty)
)
(Node 9
(Node 8 Empty Empty)
(Node 10 Empty Empty)
)
ghci> F.foldl (+) 0 testTree
42
ghci> F.foldl (*) 1 testTree
64800
现在我很困惑。没有为Trees编写foldl或foldr的实现。这些函数看起来有点像foldmap,但是将初始累加器作为树的头部,然后将foldMapping放在适当的monoid上,但它实际上不能像这样工作,因为foldl和foldr占用的功能比monoids'+'和'*'作为参数。 foldl和foldr实际上在哪里实现,它们如何工作,以及为什么定义foldMap会使它们存在?
答案 0 :(得分:14)
看看source of Foldable
。它使用foldr
来定义foldMap
,反之亦然,所以它足以定义一个对您来说更方便的那个(尽管实现两者都可以为您带来一些性能优势):
foldr :: (a -> b -> b) -> b -> t a -> b
foldr f z t = appEndo (foldMap (Endo . f) t) z
让我们来看一下这里的例子。假设我们即将折叠列表[i, j, k]
。 f
和z
的右侧折叠是
f i (f j (f k z))
这可以表示为
(f i . f j . f k) z
使用f
,我们会将列表中的每个元素转换为b
上的endomorphism并将它们组合在一起。现在,endomorphisms形成一个monoid,使用Endo
在Haskell中表示:mempty
只是id
而mappend
是.
。所以我们可以将其重写为
appEndo (Endo (f i) `mappend` Endo (f j) `mappend` Endo (f k)) z
我们可以将内部部分表达为foldMap (Endo . f) [i, j, k]
。
总结:关键的想法是某些域上的内同态形成一个幺半群,f :: a -> (b -> b)
将a
的元素映射到b
上的内同态。
反向表示为
foldMap f = foldr (mappend . f) mempty
我们f :: a -> m
其中m
是一个幺半群,并且使用mappend
撰写mappend . f :: a -> (m -> m)
,我们得到x
,其中a
类型为m
{1}}并在u :: m
上构建一个将mappend (f u) k
转换为{{1}}的函数。然后它使用此函数折叠结构的所有元素。
答案 1 :(得分:3)
来自http://hackage.haskell.org/packages/archive/base/latest/doc/html/src/Data-Foldable.html#Foldable:
class Foldable t where
...
foldMap :: Monoid m => (a -> m) -> t a -> m
foldMap f = foldr (mappend . f) mempty
...
foldr :: (a -> b -> b) -> b -> t a -> b
foldr f z t = appEndo (foldMap (Endo . f) t) z
...
foldl :: (a -> b -> a) -> a -> t b -> a
foldl f z t = appEndo (getDual (foldMap (Dual . Endo . flip f) t)) z
所以你有默认的实现(在这种情况下甚至是循环)。这就是为什么有一个评论:“最小的完整定义:foldMap或foldr。”在Foldable
类型类的说明中(请参阅http://hackage.haskell.org/packages/archive/base/latest/doc/html/Data-Foldable.html)
此技术的一个更简单的示例是Eq
类型类,其中(==)
和(/=)
是相互定义的,但当然您需要实现至少一个它们在一个实例中(否则你会得到一个无限循环)。