【问题标题】:Where do the foldl/foldr implementations of Foldable come from for binary trees in haskell?对于haskell中的二叉树,Foldable的foldl / foldr实现来自哪里?
【发布时间】:2013-05-26 08:11:50
【问题描述】:

我正在学习“Learn You a Haskell”,我正在学习 monoids 部分。在本节中,作者为一棵树定义 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 在适当的幺半群上,但它实际上不能像这样工作,因为 foldl 和 foldr 比幺半群 '+' 和 '*' 作为参数。 foldl 和 foldr 实际在哪里实现,它们是如何工作的,为什么定义 foldMap 会导致它们存在?

【问题讨论】:

  • 你看过 Data.Foldable 的源代码吗? Foldable 类中的定义应该足以回答您的问题。
  • Haskell 类型类可以有一些方法的默认实现。在这一点上,它们类似于 mixin 在其他语言中的工作方式。例如,在 Ruby 中,您只需定义 即可访问 Comparable 的其余方法。

标签: haskell tree fold monoids


【解决方案1】:

看看source of Foldable。它使用foldMap 定义foldr,反之亦然,因此定义一个对您来说更方便的就足够了(尽管实现两者都可以为您带来一些性能优势):

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 并将它们组合在一起。现在内同态形成一个幺半群,在 Haskell 中使用Endo 表示:它的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),它接受一个a类型的元素x并在m上构造一个函数将u :: m 转换为mappend (f u) k。然后它使用这个函数折叠结构的所有元素。

【讨论】:

  • 当你说“...进入b上的自同态”时,你确实不是指的是[a, b, c]列表中的b,而是@ 987654357@ 来自foldr :: (a -> b -> b) ...。也许带有[j, k, l] 的列表(例如)会让你的解释对新手更清楚
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2016-09-04
    • 2014-06-12
    • 1970-01-01
    • 2015-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-09
    相关资源
    最近更新 更多