foldable的foldl / foldr实现来自haskell中的二叉树?

时间:2013-05-26 08:11:50

标签: haskell tree fold monoids

我正在通过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会使它们存在?

2 个答案:

答案 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]fz的右侧折叠是

f i (f j (f k z))

这可以表示为

(f i . f j . f k) z

使用f,我们会将列表中的每个元素转换为b上的endomorphism并将它们组合在一起。现在,endomorphisms形成一个monoid,使用Endo在Haskell中表示:mempty只是idmappend.。所以我们可以将其重写为

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类型类,其中(==)(/=)是相互定义的,但当然您需要实现至少一个它们在一个实例中(否则你会得到一个无限循环)。