Fold (高阶函数)

在函数式编程中，折叠（fold），也称为归约（reduce）、积累（accumulate）、聚集（aggregate）、压缩（compress）或注入（inject），指称一组高阶函数，它们分析递归数据结构并通过使用给定组合运算，将递归的处理它的构成部件、建造一个返回值的结果重组起来。典型的，要向折叠提供一个组合函数，一个数据结构的顶端，和可能的在特定条件下使用的某些缺省值。折叠接着以系统性方式使用这个函数，进行组合这个数据结构的层级中的元素。
折叠在某种意义上是（unfold）的对偶，它接受一个种子值并共递归的应用一个函数，来确定如何进一步的构造一个共递归的数据结构。折叠递归的分解这个数据结构，在每个节点应用一个组合函数于它的终结值和递归结果之上，用得到这个结果替代它。折叠是，而展开是。
作为结构性变换.
折叠可以视为是将数据结构的结构性构件一致性的替代为函数和值。例如在很多函数式语言中，列表是用两个原语建造的：任何列表要么是一个空列表，通常叫做codice_1（codice_2），要么是通过将一个元素前缀于另一个列表之前来构造的，通过应用codice_3函数（在Haskell中写为冒号codice_4），建立所谓的cons，比如codice_5。可以将在列表上的折叠看作是将这个列表的末端的codice_1替代为一个特殊的值，并用一个特殊函数替代每个codice_3。
使用Haskell作为例子，可以用几个等式公式化出右折叠codice_8：
foldr :: (a -&gt; b -&gt; b) -&gt; b -&gt; [a] -&gt; b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
如果列表为空，结果是初始值codice_9。如果不是空，应用codice_10于第一个元素和折叠余下列表的结果。这种替代可以图示如下：
以一致性风格进行结构性变换的另一种方式，左折叠codice_11：
foldl :: (b -&gt; a -&gt; b) -&gt; b -&gt; [a] -&gt; b
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
如果列表为空，结果是初始值。如果不是空，将应用codice_10于旧初始值和第一个元素的结果作为新初始值，折叠它和余下列表。这种替代可以图示如下：
这两个示意图展示在一个列表上的右折叠和左折叠。逐个元素的使用codice_3构造的列表，每个节点的左链接是终结值，而右链接是另一个节点，右折叠之后保持这种形态。左折叠之后，承载函数的每个节点的右链接是终结值，而左链接是另一个节点。
这两个示意图还突出了如下事实：是在列表上的同一函数（按Lisp说法是“浅层复制”），因为替代codice_3为codice_3并替代codice_1为codice_1不改变结果。并提示了逆转一个列表的一种容易的方法：。注意这里用codice_18函数将给codice_3的二个参数进行了翻转。codice_18只在Haskell这样的语言中需要，它翻转给codice_11的组合函数的实际参数的次序，不像在Scheme中，这里对codice_11和codice_8二者的组合函数使用相同的实际参数次序。
另一个易得的结果是，高阶函数map也可以凭借codice_8书写，通过将要作用在元素上的那个函数复合于codice_3，即是：
map f = foldr ((:) . f) []
这里的点号codice_26是指示的算子。
通过演示在线性列表上的折叠函数的构造方式，就会得到启发在其他代数数据类型和结构比如各种树之上设计类似的折叠函数。我们可以写一个高阶函数，递归的将数据类型的构造子替代为所提供的函数，并将任何这个类型的常量值替代为所提供的值。这种函数一般称为。
在列表上.
用加法算子折叠列表codice_27会得到结果codice_28, 它是这个列表元素的总和。粗略近似的说，折叠将这个列表中逗号替代成了codice_29运算，得出了codice_30。
在上述的例子中，codice_29是结合律运算，所有最终的结果不管如何加括号都是相同的，尽管括号导致的特定计算次序是不同的。在非结合律二元运算的一般情况下，组合元素的次序可以影响最终的结果值。在列表之上，有二个面向的进行这个工作的方式：要么组合第一个元素和递归的组合余下列表的结果（叫做右折叠），要么组合递归的组合除了最后一个元素的所有元素的结果和最后一个元素（叫做左折叠）。着对应于一个二元算子要么是右结合的要么是左结合的，采用了Haskell或Prolog的术语。使用右折叠，合计将加括号为codice_32，而使用左折叠它将加括号为codice_33。
实际上，在使用右折叠的情况下有一个初始值同列表的最后一个元素组合，在使用左折叠的情况下有一个初始值同和列表的第一个元素组合，是方便和自然的。在上述的例子中，值codice_34（加法单位元）可以被选择为初始值，对于右折叠得到codice_35，对于左折叠得到codice_36。对于乘法，选择codice_37（乘法单位元）作为初始值，这将得出codice_38。 
在组合函数codice_10的类型是不对称的情况下，比如codice_40，就是说如果结果的类型不同于列表元素的类型，使用初始值是必需的。要使一个线性的应用链成为可能，使用的这个初始值的类型必须同于codice_10的结果的类型。不管是右折叠还是左折叠，它的类型都确定为组合函数的参数所预期的类型。如果第二个参数必须与结果相同类型，则codice_10可以被看作是右结合的，如果是第一个参数则为左结合的。那些使用对称类型二元运算的折叠，它的二个参数的类型和它的结果的类型必须相同。
树状折叠.
在组合函数是个原群的情况下，就是说它的类型是对称的，比如codice_43，就是说结果的类型同于列表元素的类型，则可以用任意方式放置括号，因而建立嵌套子表达式的“树”，比如codice_44。如果二元运算codice_10是结合律的，则这个值将是良好定义的，就是对于任何加括号情况，都是相同的，尽管如何计算它的运算细节会是不同的。如果codice_10是非严格求值的，这可能在效能上有重大影响。
线性折叠是面向节点的，并以一致方式对列表的每个节点进行运算；而树状折叠是面向整个列表的，并以一致方式跨越节点“群”进行运算。
列表可以按树状风格来折叠，分别对于有限和不明确定义的列表二者：
foldt :: (a -&gt; a -&gt; a) -&gt; a -&gt; [a] -&gt; a
foldt f z [] = z
foldt f z [x] = f x z
foldt f z xs = foldt f z (pairs f xs)
foldi :: (a -&gt; a -&gt; a) -&gt; a -&gt; [a] -&gt; a 
foldi f z [] = z
foldi f z (x:xs) = f x (foldi f z (pairs f xs))
pairs :: (a -&gt; a -&gt; a) -&gt; [a] -&gt; [a] 
pairs f (x:y:t) = f x y : pairs f t
pairs _ t = t
在codice_47函数的情况下，为了避免在不明确定义的列表上失控求值，函数codice_10必须“不总是”需求它的第二个参数的值，至少不是所有都要，或者不是立即就要。
非空列表的特殊折叠.
人们经常希望选择codice_10的单位元作为初始值codice_9。在没有合适的初始值的时候，例如在想要把计算它的二个参数的极大值的函数，折叠到一个非空列表之上，来得到这个列表的极大值，可以用codice_8和codice_11的变体，它们分别使用这个列表的最后一个和第一个元素作为初始值。在Haskell和其他一些语言中，它们叫做codice_53和codice_54，这里的“1”所指的是自动提供初始元素，和它们所应用到的列表至少要有一个元素的事实。
foldl1 f [x] = x
foldl1 f (x:y:xs) = foldl1 f (f x y : xs)
foldr1 f [x] = x
foldr1 f (x:xs) = f x (foldr1 f xs)
foldt1 f [x] = x
foldt1 f (x:y:xs) = foldt1 f (f x y : pairs f xs)
foldi1 f [x] = x
foldi1 f (x:xs) = f x (foldi1 f (pairs f xs))
例子.
使用Haskell解释器，折叠进行的结构性变换可以用构造一个字符串来展示：
λ&gt; foldr (\x y -&gt; concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(1+(2+(3+(4+(5+(6+(7+(8+(9+(10+(11+(12+(13+0)))))))))))))"
λ&gt; foldl (\x y -&gt; concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(((((((((((((0+1)+2)+3)+4)+5)+6)+7)+8)+9)+10)+11)+12)+13)"
λ&gt; foldt (\x y -&gt; concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(((((1+2)+(3+4))+((5+6)+(7+8)))+(((9+10)+(11+12))+13))+0)"
λ&gt; foldi (\x y -&gt; concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(1+((2+3)+(((4+5)+(6+7))+((((8+9)+(10+11))+(12+13))+0))))"
无限树状折叠，可以用Haskell的通过埃拉托斯特尼筛法的递归素数生成来演示：
primes = 2 : _Y ((3 :) . minus [5,7..] . foldi (\(x:xs) ys -&gt; x : union xs ys) [] 
 . map (\p-&gt; [p*p, p*p+2*p..]))
_Y g = g (_Y g) -- = g . g . g . g . ...
这里的函数codice_55以本地方式运算于有序列表之上来高效的产生它们的并集，而codice_56 做它们的集合差。
对于有限列表，归并排序（和它的去除重复变体codice_57）可以使用树状折叠轻易的定义为：
mergesort xs = foldt merge [] [[x] | x &lt;- xs]
nubsort xs = foldt union [] [[x] | x &lt;- xs]
采用的函数codice_58是codice_55的保留重复的变体。
函数codice_60和codice_61也可以通过折叠定义为：
head = foldr (\x r -&gt; x) (error "head: Empty list")
last = foldl (\a x -&gt; x) (error "last: Empty list")
求值次序考虑.
在采用[[惰性求值|惰性]]或非严格求值策略的场合下，codice_8将立即返回codice_10在列表头部和折叠余下列表的递归案例上的这个应用。因此，如果codice_10能够产生其结果的一部份，而不需要引用到递归案例，它在codice_10的“右”也就是第二个实际参数上，而余下的结果永不需要，那么递归就会停止，例如上节定义的codice_60函数。这允许右折叠可以运算在无限列表之上。与之相反，codice_11将立即的调用具有新参数的自身，直到它达到了列表的末端。这种[[尾调用|尾递归]]可以高效的编译为循环，但是根本不能处理无限列表，它会永久递归于[[无限循环]]。
已经到达列表的末端之后，codice_11有效的建造了一个“表达式”，它是嵌套的左深化codice_10应用，它被提供给调用者进行求值。如果函数codice_10首先引用它的第二个参数，并且能够产生其结果的一部份，而不需要引用到递归案例，这里是在它的“左”也就是第一个实际参数上，那么递归会停止，例如上节定义的codice_61函数。这意味着尽管codice_8递归“于右侧”，它允许惰性组合函数来从左至右检查列表的元素；而反过来，尽管codice_11递归“于左侧”，它允许惰性组合函数从从右至左检查列表的元素。
逆转一个列表也是尾递归的，它可以使用实现。在有限列表上，这意味着左折叠和逆转可以复合起来以尾递归方式进行右折叠，通过修改codice_10使它逆转其参数的次序，例如，这样尾递归的建造出的一个表达式的表示同于右折叠所建造的。
额外的中间列表结构可以通过形似的手段去除：；另一个在也类似：。它们也常被写为：
foldr :: (a -&gt; b -&gt; b) -&gt; b -&gt; [a] -&gt; b
foldr f z xs = foldl (\k x y -&gt; k (f x y)) id xs z
foldl :: (b -&gt; a -&gt; b) -&gt; b -&gt; [a] -&gt; b
foldl f z xs = foldr (\x k y -&gt; k (f y x)) id xs z
另一个技术要点是，在使用惰性求值的左折叠的情况下，新初始化参数在进行递归调用之前是不被求值的。在达到了列表末端并尝试计算结果的巨大的表达式的时候，这可能导致堆栈溢出。为此，这种语言经常提供左折叠的更严格变体，它在进行递归调用之前强制的求值初始化参数。在Haskell中它是Data.List库里的codice_75函数（注意这个撇号读作“prime”）。需要意识到一个事实，强制一个值用惰性数据构造子建造，其自身不会自动的强制它的构件。结合于尾递归，这种折叠接近了循环的效率，在惰性求值的最终结果是不可能或不可取的时候，确保了恒定空间运算。
在各种语言中.
! 语言 !! 左fold !! 右fold !! 无初始值左fold !! 无初始值右fold !! Unfold !! 注释
[[Category:高阶函数]]
[[Category:递归]]
[[Category:编程语言比较]]
[[Category:编程中的迭代]]