文件夹如何工作?


74

谁能解释foldr工作原理?

请看以下示例:

Prelude> foldr (-) 54 [10, 11]
53
Prelude> foldr (\x y -> (x+y)/2) 54 [12, 4, 10, 6]
12.0

我对这些处决感到困惑。有什么建议?

Answers:


90

foldr从列表的右端开始,并使用您提供的函数将每个列表条目与累加器值组合在一起。结果是在所有列表元素中“折叠”之后累加器的最终值。它的类型是:

foldr :: (a -> b -> b) -> b -> [a] -> b

从中可以看到list元素(类型为a)是给定函数的第一个参数,而accumulator(类型为b)是第二个。

对于第一个示例:

Starting accumulator = 54
11 -   54  = -43
10 - (-43) =  53

        ^  Result from the previous line

 ^ Next list item

因此,您得到的答案是53。

第二个例子:

Starting accumulator = 54
(6  + 54) / 2 = 30
(10 + 30) / 2 = 20
(4  + 20) / 2 = 12
(12 + 12) / 2 = 12

所以结果是12。

编辑:我的意思是要添加,这是针对有限列表的。 foldr也可以在无限列表上使用,但我认为最好是先绕开有限情况。


2
您确定文件夹可以在无限列表上使用吗?以我的理解,方括号表示必须首先评估最后一个元素。
杰夫·福斯特

10
例如,您可以使用文件夹来实现“地图”,即使在无限列表上也可以使用。之所以可行,是因为(:)在其第二个参数或英语中是非严格的,因为当您沿着结果列表的末尾移动时,它的末尾可以保持不变。周围有许多网页比我能更好地解释这一点,但是正如我所说,要花一些力气才能使您有所了解。协调文件夹的行为方式定义方式并非易事。
内夫鲁毕(Nefrubyr),2009年

6
这是完全错误的。foldr不是“从右边开始”。它是右关联的。您可以通过阅读hackage.haskell.org/package/base-4.10.0.0/docs/src/…[]实例的源代码进行验证Foldable
sara

139

理解文件夹的最简单方法是不用糖就重写要折叠的列表。

[1,2,3,4,5] => 1:(2:(3:(4:(5:[]))))

现在的foldr f x作用是:finfix形式替换[]x然后评估结果。

例如:

sum [1,2,3] = foldr (+) 0 [1,2,3]

[1,2,3] === 1:(2:(3:[]))

所以

sum [1,2,3] === 1+(2+(3+0)) = 6

9
确实是最好的解释。同埃里克·梅耶尔如何描述它,即foldr相似只不过是一个替代的基本情况即,[]cons与您选择的蓄电池和功能操作。
zeusdeux 2014年

这完美地显示了清单是如何以lambda演算的Church表示法工作的-基本上,清单是未应用的foldr,因此用户可以决定如何处理“缺点”和“零”情况
radrow

42

它有助于理解之间的区别foldrfoldl。为什么foldr叫“向右折”?

最初我以为是因为它从右到左消耗了元素。不过,两人都foldrfoldl消费清单由左到右。

  • foldl 从左到右评估(左关联)
  • foldr 从右到左评估(右关联)

我们可以通过一个使用关联性很重要的运算符的示例来明确区分。我们可以使用人类示例,例如运算符“ eats”:

foodChain = (human : (shark : (fish : (algae : []))))

foldl step [] foodChain
  where step eater food = eater `eats` food  -- note that "eater" is the accumulator and "food" is the element

foldl `eats` [] (human : (shark : (fish : (algae : []))))
  == foldl eats (human `eats` shark)                              (fish : (algae : []))
  == foldl eats ((human `eats` shark) `eats` fish)                (algae : [])
  == foldl eats (((human `eats` shark) `eats` fish) `eats` algae) []
  ==            (((human `eats` shark) `eats` fish) `eats` algae)

这样做的语义foldl是:一个人吃鲨鱼,然后那个吃了鲨鱼的人又吃了一些鱼,等等。吃者是蓄积者。

将此与:

foldr step [] foodChain
    where step food eater = eater `eats` food.   -- note that "eater" is the element and "food" is the accumulator

foldr `eats` [] (human : (shark : (fish : (algae : []))))
  == foldr eats (human `eats` shark)                              (fish : (algae : []))))
  == foldr eats (human `eats` (shark `eats` (fish))               (algae : [])
  == foldr eats (human `eats` (shark `eats` (fish `eats` algae))) []
  ==            (human `eats` (shark `eats` (fish `eats` algae) 

这样做的含义foldr是:一个人吃了已经吃了一条鱼的鲨鱼,而这条鱼已经吃了一些藻类。食物是蓄积者。

这两个foldlfoldr“剥离”食由左到右,所以这不是我们所说的与foldl为理由“左折”。相反,评估的顺序很重要。


22

考虑一下foldr的非常定义

 -- if the list is empty, the result is the initial value z
 foldr f z []     = z                  
 -- if not, apply f to the first element and the result of folding the rest 
 foldr f z (x:xs) = f x (foldr f z xs)

因此,例如foldr (-) 54 [10,11]必须等于(-) 10 (foldr (-) 54 [11]),即再次扩展等于(-) 10 ((-) 11 54)。因此内部运算是11 - 54,即-43; 并且外部操作是10 - (-43),即10 + 43,因此53如您所观察。对于第二种情况,请执行类似的步骤,然后您将再次看到结果的形式!


源代码中,定义为foldr f z t = appEndo (foldMap (Endo #. f) t) z
学生

17

foldr表示从右侧折叠,因此foldr (-) 0 [1, 2, 3]产生(1 - (2 - (3 - 0)))。比较foldl产生(((0 - 1) - 2) - 3)

当运算符不交换时 foldlfoldr将得到不同的结果。

在您的情况下,第一个示例扩展(10 - (11 - 54)) 为53。


7

一个简单的理解方法foldr是:它将每个列表构造函数替换为所提供函数的应用程序。您的第一个示例将转换为:

10 - (11 - 54)

从:

10 : (11 : [])

我从Haskell Wikibook得到的一个很好的建议可能在这里有用:

通常,应该foldr在可能是无限的列表或折叠构成数据结构foldl'的位置上使用列表,并且如果列表已知是有限的并且归结为单个值。foldl(没有滴答声)根本不应该使用。



2

我认为以简单的方式实现map,foldl和foldr有助于解释它们的工作方式。实例有助于我们的理解。

  myMap f [] = []
  myMap f (x:xs) = f x : myMap f xs

  myFoldL f i [] = i
  myFoldL f i (x:xs) = myFoldL f (f i x) xs

  > tail [1,2,3,4] ==> [2,3,4]
  > last [1,2,3,4] ==> 4
  > head [1,2,3,4] ==> 1
  > init [1,2,3,4] ==> [1,2,3]

  -- where f is a function,
  --  acc is an accumulator which is given initially
  --  l is a list.
  --
  myFoldR' f acc [] = acc
  myFoldR' f acc l = myFoldR' f (f acc (last l)) (init l)

  myFoldR f z []     = z
  myFoldR f z (x:xs) = f x (myFoldR f z xs)

  > map (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]
  > myMap (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]

  > foldl (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125
  > myFoldL (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125

    foldl from above: Starting accumulator = 54
      (12  + 54) / 2 = 33
      (4 + 33) / 2 = 18.5
      (10  + 18.5) / 2 = 14.25
      (6 + 14.25) / 2 = 10.125`

 > foldr (++) "5" ["1", "2", "3", "4"] ==> "12345"

 > foldl (++) "5" ["1", "2", "3", "4"] ==> “51234"

 > foldr (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
 > myFoldR' (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
 > myFoldR (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12

    foldr from above: Starting accumulator = 54
        (6  + 54) / 2 = 30
        (10 + 30) / 2 = 20
        (4  + 20) / 2 = 12
        (12 + 12) / 2 = 12

1

好吧,让我们看一下参数:

  • 一个函数(需要一个列表元素和一个与其返回的值具有相同类型的值(可能的部分结果));
  • 空列表特殊情况的初始结果的规范
  • 一个列表;

返回值:

  • 一些最终结果

它首先将函数应用于列表中的最后一个元素和空列表结果。然后,它将此结果和上一个元素重新应用到函数中,依此类推,直到它获取一些当前结果和列表的第一个元素以返回最终结果。

Fold使用带有一个元素的函数和一些先前的折叠结果,在初始结果周围“折叠”一个列表。对每个元素重复此操作。因此,foldr从列表的末尾或右侧开始执行此操作。

folr f emptyresult [1,2,3,4]变成 f(1, f(2, f(3, f(4, emptyresult) ) ) ) 。现在只需在评估中遵循括号即可,仅此而已。

需要注意的重要一件事是,提供的函数f必须处理其自己的返回值作为其第二个参数,这意味着两个函数必须具有相同的类型。

资料来源:我的文章,如果您认为这可能会有所帮助,那么我将从命令性的未经处理的javascript角度进行研究。


1

仔细阅读此处提供的其他答案并进行比较,应该已经清楚了这一点,但值得注意的是,被接受的答案可能会对初学者产生误导。正如其他评论者所指出的那样,在Haskell中执行的计算文件夹并不“位于列表的右端”;否则,foldr就永远无法在无限列表上工作(在正确的条件下,它可以在Haskell中完成)。

Haskell函数的源代码foldr应明确说明:

foldr k z = go
          where
            go []     = z
            go (y:ys) = y `k` go ys

每个递归计算将最左侧的原子列表项与列表尾部的递归计算结合在一起,即:

a\[1\] `f` (a[2] `f` (a[3] `f` ... (a[n-1] `f` a[n])  ...))

a[n]初始累加器在哪里。

因为还原是“在Haskell中懒惰地完成的”,所以实际上是从左侧开始的。这就是我们所说的“惰性评估”,这是Haskell的显着特征。这对于理解Haskell的运作非常重要foldr;因为实际上是从左开始递归地foldr建立和减少计算,所以短路的二进制运算符有机会这样做,允许在适当的情况下减少无限列表。foldr

它会导致远不如混乱初学者说相当的r(“正确”)和l(“左”)中foldr,并foldl右联左结合,要么离开它,或者试着解释Haskell的懒惰的影响评估机制。

为了处理您的示例,遵循foldr源代码,我们构建以下表达式:

Prelude> foldr (-) 54 [10, 11]

->

10 - [11 - 54] = 53

然后再次:

foldr (\x y -> (x + y) / 2) 54 [12, 4, 10, 6]

->

(12 + (4 + (10 + (6 + 54) / 2) / 2) / 2) / 2 = 12
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.