Movatterモバイル変換


[0]ホーム

URL:


Jump to content
WikipediaThe Free Encyclopedia
Search

Fold (higher-order function)

From Wikipedia, the free encyclopedia
Family of higher-order functions
Comparison of
programming languages

Infunctional programming, afold is ahigher-order function thatanalyzes arecursive data structure and, through use of a given combining operation, recombines the results ofrecursively processing its constituent parts, building up a return value. Fold is also termed asreduce,accumulate,aggregate,compress, orinject. Typically, a fold is presented with a combiningfunction, a topnode of adata structure, and possibly some default values to be used under certain conditions. The fold then proceeds to combine elements of the data structure'shierarchy, using the function in a systematic way.

Folds are in a sense dual tounfolds, which take aseed value and apply a functioncorecursively to decide how to progressively construct a corecursive data structure, whereas a fold recursively breaks that structure down, replacing it with the results of applying a combining function at each node on itsterminal values and the recursive results (catamorphism, versusanamorphism of unfolds).

As structural transformations

[edit]
icon
This sectiondoes notcite anysources. Please helpimprove this section byadding citations to reliable sources. Unsourced material may be challenged andremoved.(June 2013) (Learn how and when to remove this message)

Folds can be regarded as consistently replacing the structural components of a data structure with functions and values.Lists, for example, are built up in many functional languages from two primitives: any list is either an empty list, commonly callednil  ([]), or is constructed by prefixing an element in front of another list, creating what is called aconsnode Cons(X1,Cons(X2,Cons(...(Cons(Xn,nil))))) ), resulting from application of acons function (written down as a colon(:) inHaskell). One can view a fold on lists asreplacing  thenil at the end of the list with a specific value, andreplacing eachcons with a specific function. These replacements can be viewed as a diagram:

There's another way to perform the structural transformation in a consistent manner, with the order of the two links of each node flipped when fed into the combining function:

These pictures illustrateright andleft fold of a list visually. They also highlight the fact thatfoldr (:) [] is the identity function on lists (ashallow copy inLisp parlance), as replacingcons withcons andnil withnil will not change the result. The left fold diagram suggests an easy way to reverse a list,foldl (flip (:)) []. Note that the parameters to cons must be flipped, because the element to add is now the right hand parameter of the combining function. Another easy result to see from this vantage-point is to write the higher-ordermap function in terms offoldr, by composing the function to act on the elements withcons, as:

mapf=foldr((:).f)[]

where the period (.) is an operator denotingfunction composition.

This way of looking at things provides a simple route to designing fold-like functions on otheralgebraic data types and structures, like various sorts of trees. One writes a function which recursively replaces the constructors of the datatype with provided functions, and any constant values of the type with provided values. Such a function is generally referred to as acatamorphism.

On lists

[edit]

The folding of the list[1,2,3,4,5] with the addition operator would result in 15, the sum of the elements of the list[1,2,3,4,5]. To a rough approximation, one can think of this fold as replacing the commas in the list with the + operation, giving1 + 2 + 3 + 4 + 5.[1]

In the example above, + is anassociative operation, so the final result will be the same regardless of parenthesization, although the specific way in which it is calculated will be different. In the general case of non-associative binary functions, the order in which the elements are combined may influence the final result's value. On lists, there are two obvious ways to carry this out: either by combining the first element with the result of recursively combining the rest (called aright fold), or by combining the result of recursively combining all elements but the last one, with the last element (called aleft fold). This corresponds to a binaryoperator being either right-associative or left-associative, inHaskell's orProlog's terminology. With a right fold, the sum would be parenthesized as1 + (2 + (3 + (4 + 5))), whereas with a left fold it would be parenthesized as(((1 + 2) + 3) + 4) + 5.

In practice, it is convenient and natural to have an initial value which in the case of a right fold is used when one reaches the end of the list, and in the case of a left fold is what is initially combined with the first element of the list. In the example above, the value 0 (theadditive identity) would be chosen as an initial value, giving1 + (2 + (3 + (4 + (5 + 0)))) for the right fold, and((((0 + 1) + 2) + 3) + 4) + 5 for the left fold. For multiplication, an initial choice of 0 wouldn't work:0 * 1 * 2 * 3 * 4 * 5 = 0. Theidentity element for multiplication is 1. This would give us the outcome1 * 1 * 2 * 3 * 4 * 5 = 120 = 5!.

Linear vs. tree-like folds

[edit]

The use of an initial value is necessary when the combining functionf  is asymmetrical in its types (e.g.a → b → b), i.e. when the type of its result is different from the type of the list's elements. Then an initial value must be used, with the same type as that off 's result, for alinear chain of applications to be possible. Whether it will be left- or right-oriented will be determined by the types expected of its arguments by the combining function. If it is the second argument that must be of the same type as the result, thenf  could be seen as a binary operation thatassociates on the right, and vice versa.

When the function is amagma, i.e. symmetrical in its types (a → a → a), and the result type is the same as the list elements' type, the parentheses may be placed in arbitrary fashion thus creating abinary tree of nested sub-expressions, e.g.,((1 + 2) + (3 + 4)) + 5. If the binary operationf  is associative this value will be well-defined, i.e., same for any parenthesization, although the operational details of how it is calculated will be different. This can have significant impact on efficiency iff  isnon-strict.

Whereas linear folds arenode-oriented and operate in a consistent manner for eachnode of alist, tree-like folds are whole-list oriented and operate in a consistent manner acrossgroups of nodes.

Special folds for non-empty lists

[edit]

One often wants to choose theidentity element of the operationf as the initial valuez. When no initial value seems appropriate, for example, when one wants to fold the function which computes the maximum of its two parameters over a non-empty list to get the maximum element of the list, there are variants offoldr andfoldl which use the last and first element of the list respectively as the initial value. In Haskell and several other languages, these are calledfoldr1 andfoldl1, the 1 making reference to the automatic provision of an initial element, and the fact that the lists they are applied to must have at least one element.

These folds use type-symmetrical binary operation: the types of both its arguments, and its result, must be the same.Richard Bird in his 2010 book proposes[2] "a general fold function on non-empty lists"foldrn which transforms its last element, by applying an additional argument function to it, into a value of the result type before starting the folding itself, and is thus able to use type-asymmetrical binary operation like the regularfoldr to produce a result of type different from the list's elements type.

Implementation

[edit]

Linear folds

[edit]

Using Haskell as an example,foldl andfoldr can be formulated in a few equations.

foldl::(b->a->b)->b->[a]->bfoldlfz[]=zfoldlfz(x:xs)=foldlf(fzx)xs

If the list is empty, the result is the initial value. If not, fold the tail of the list using as new initial value the result of applying f to the old initial value and the first element.

foldr::(a->b->b)->b->[a]->bfoldrfz[]=zfoldrfz(x:xs)=fx(foldrfzxs)

If the list is empty, the result is the initial value z. If not, apply f to the first element and the result of folding the rest.

Tree-like folds

[edit]

Lists can be folded over in a tree-like fashion, both for finite and for indefinitely defined lists:

foldtfz[]=zfoldtfz[x]=fxzfoldtfzxs=foldtfz(pairsfxs)foldifz[]=zfoldifz(x:xs)=fx(foldifz(pairsfxs))pairsf(x:y:t)=fxy:pairsftpairs_t=t

In the case offoldi function, to avoid its runaway evaluation onindefinitely defined lists the functionf mustnot always demand its second argument's value, at least not all of it, or not immediately (seeexample below).

Folds for non-empty lists

[edit]
foldl1f[x]=xfoldl1f(x:y:xs)=foldl1f(fxy:xs)foldr1f[x]=xfoldr1f(x:xs)=fx(foldr1fxs)foldt1f[x]=xfoldt1f(x:y:xs)=foldt1f(fxy:pairsfxs)foldi1f[x]=xfoldi1f(x:xs)=fx(foldi1f(pairsfxs))

Evaluation order considerations

[edit]

In the presence oflazy, ornon-strict evaluation,foldr will immediately return the application off to the head of the list and the recursive case of folding over the rest of the list. Thus, iff is able to produce some part of its result without reference to the recursive case on its "right" i.e., in itssecond argument, and the rest of the result is never demanded, then the recursion will stop (e.g.,head==foldr(\ab->a)(error"empty list")). This allows right folds to operate on infinite lists. By contrast,foldl will immediately call itself with new parameters until it reaches the end of the list. Thistail recursion can be efficiently compiled as a loop, but can't deal with infinite lists at all — it will recurse forever in aninfinite loop.

Having reached the end of the list, anexpression is in effect built byfoldl of nested left-deepeningf-applications, which is then presented to the caller to be evaluated. Were the functionf to refer to its second argument first here, and be able to produce some part of its result without reference to the recursive case (here, on itsleft i.e., in itsfirst argument), then the recursion would stop. This means that whilefoldr recurseson the right, it allows for a lazy combining function to inspect list's elements from the left; and conversely, whilefoldl recurseson the left, it allows for a lazy combining function to inspect list's elements from the right, if it so chooses (e.g.,last==foldl(\ab->b)(error"empty list")).

Reversing a list is also tail-recursive (it can be implemented usingrev=foldl(\ysx->x:ys)[]). Onfinite lists, that means that left-fold and reverse can be composed to perform a right fold in a tail-recursive way (cf. 1+>(2+>(3+>0))==((0<+3)<+2)<+1), with a modification to the functionf so it reverses the order of its arguments (i.e.,foldrfz==foldl(flipf)z.foldl(flip(:))[]), tail-recursively building a representation of expression that right-fold would build. The extraneous intermediate list structure can be eliminated with thecontinuation-passing style technique,foldrfzxs==foldl(\kx->k.fx)idxsz; similarly,foldlfzxs==foldr(\xk->k.flipfx)idxsz ( flip is only needed in languages like Haskell with its flipped order of arguments to the combining function offoldl unlike e.g., in Scheme where the same order of arguments is used for combining functions to bothfoldl andfoldr).

Another technical point is that, in the case of left folds using lazy evaluation, the new initial parameter is not being evaluated before the recursive call is made. This can lead to stack overflows when one reaches the end of the list and tries to evaluate the resulting potentially gigantic expression. For this reason, such languages often provide a stricter variant of left folding which forces the evaluation of the initial parameter before making the recursive call. In Haskell this is thefoldl' (note the apostrophe, pronounced 'prime') function in theData.List library (one needs to be aware of the fact though that forcing a value built with a lazy data constructor won't force its constituents automatically by itself). Combined with tail recursion, such folds approach the efficiency of loops, ensuring constant space operation, when lazy evaluation of the final result is impossible or undesirable.

Examples

[edit]

Using aHaskell interpreter, the structural transformations which fold functions perform can be illustrated by constructing a string:

λ>foldr(\xy->concat["(",x,"+",y,")"])"0"(mapshow[1..13])"(1+(2+(3+(4+(5+(6+(7+(8+(9+(10+(11+(12+(13+0)))))))))))))"λ>foldl(\xy->concat["(",x,"+",y,")"])"0"(mapshow[1..13])"(((((((((((((0+1)+2)+3)+4)+5)+6)+7)+8)+9)+10)+11)+12)+13)"λ>foldt(\xy->concat["(",x,"+",y,")"])"0"(mapshow[1..13])"(((((1+2)+(3+4))+((5+6)+(7+8)))+(((9+10)+(11+12))+13))+0)"λ>foldi(\xy->concat["(",x,"+",y,")"])"0"(mapshow[1..13])"(1+((2+3)+(((4+5)+(6+7))+((((8+9)+(10+11))+(12+13))+0))))"

Infinite tree-like folding is demonstrated e.g., inrecursive primes production byunbounded sieve of Eratosthenes inHaskell:

primes=2:_Y((3:).minus[5,7..].foldi(\(x:xs)ys->x:unionxsys)[].map(\p->[p*p,p*p+2*p..]))_Yg=g(_Yg)-- = g . g . g . g . ...

where the functionunion operates on ordered lists in a local manner to efficiently produce theirset union, andminus theirset difference.

A finite prefix of primes is concisely defined as a folding of set difference operation over the lists of enumerated multiples of integers, as

primesTon=foldl1minus[[2*x,3*x..n]|x<-[1..n]]

For finite lists, e.g.,merge sort (and its duplicates-removing variety,nubsort) could be easily defined using tree-like folding as

mergesortxs=foldtmerge[][[x]|x<-xs]nubsortxs=foldtunion[][[x]|x<-xs]

with the functionmerge a duplicates-preserving variant ofunion.

Functionshead andlast could have been defined through folding as

head=foldr(\xr->x)(error"head: Empty list")last=foldl(\ax->x)(error"last: Empty list")

In various languages

[edit]
LanguageLeft foldRight foldLeft fold without initial valueRight fold without initial valueUnfoldNotes
APLfunc⍨/initval,vectorfunc/vector,initvalfunc⍨/vectorfunc/vector
C#3.0ienum.Aggregate(initval,func)ienum.Reverse().Aggregate(initval,func)ienum.Aggregate(func)ienum.Reverse().Aggregate(func)Aggregate is anextension method
ienum is anIEnumerable<T>
Similarly in all .NET languages
C++std::accumulate(begin,end,initval,func)std::accumulate(rbegin,rend,initval,func)in header<numeric>
begin,end,rbegin,rend are iterators
func can be afunction pointer or afunction object
C++17(initvalop ...oppack)(packop ...opinitval)(...oppack)(packop ...)Fold expression (only forvariadic templates):op is a binary operator (bothops must be the same, e.g.,(std::cout<<...<<args)),pack is an unexpanded parameter pack.
C++23std::ranges::fold_left(range,initval,func)std::ranges::fold_right(range,initval,func)std::ranges::fold_left_first(range,func)std::ranges::fold_right_last(range,func)Bothstd::ranges::fold_left_first andstd::ranges::fold_right_last returnstd::optional considering the emptiness ofrange.
CFMLobj.reduce(func,initial)obj.reduce(func)Wherefunc receives as arguments the result of the previous operation (or theinitial value on the first iteration); the current item; the current item's index or key; and a reference to theobj
Clojure(reducefuncinitvallist)(reducefuncinitval (reverselist))(reducefunclist)(reducefunc (reverselist))See alsoclojure.core.reducers/fold
Common Lisp(reducefunclist :initial-valueinitval)(reducefunclist :from-end t :initial-valueinitval)(reducefunclist)(reducefunclist :from-end t)
Dreduce!func(initval,list)reduce!func(initval,list.reverse)reduce!func(list)reduce!func(list.reverse)in modulestd.algorithm
ElixirList.foldl(list,acc,fun)List.foldr(list,acc,fun)Seedocumentation for example usage
ElmList.foldl(Fun,Accumulator,List)List.foldr(Fun,Accumulator,List)See also List API[1]
Erlanglists:foldl(Fun,Accumulator,List)lists:foldr(Fun,Accumulator,List)
F#List.foldfuncinitvallist
Seq.foldfuncinitvalsequence
List.foldBackfunclistinitvalList.reducefunclist
Seq.reducefuncsequence
List.reduceBackfunclistSeq.unfoldfuncinitval
Gleamlist.fold(list,initial,func)
yielder.fold(yielder,initial,func)
list.fold_right(list,initial,func)list.reduce(list,func)
yielder.reduce(yielder,func)
yielder.unfold(initial,func)
GosuIterable.fold(f(agg, e))
Iterable.reduce(init,f(agg, e))
Iterable.partition(f(e))
All areextension methods on Java'sIterable interface, arrays are also supported
Groovylist.inject(initval,func)list.reverse().inject(initval,func)list.inject(func)list.reverse().inject(func)
Haskellfoldlfuncinitvallistfoldrfuncinitvallistfoldl1funclistfoldr1funclistunfoldrfuncinitvalForfoldl, the folding function takes arguments in the opposite order as that forfoldr.
HaxeLambda.fold(iterable,func,initval)
Jverb~/|.initval,arrayverb/array,initvalverb~/|.arrayverb/arrayu/y applies the dyad u between the items of y."J Dictionary: Insert"
Java 8+stream.reduce(initval,func)stream.reduce(func)
JavaScript 1.8
ECMAScript 5
array.reduce(func,initval)[3]array.reduceRight(func,initVal)array.reduce(func)array.reduceRight(func)The reducer main arguments are accumulator and current value, and we can use optional arguments like index and array.array.reduceRight((acc,value,idx,array)=>{},initvalue)
Juliafoldl(op,itr; [init])foldr(op,itr; [init])foldl(op,itr)foldr(op,itr)
KotlinIterable.fold(initval,func)Iterable.foldRight(initval,func)Iterable.reduce(func)Iterable.reduceRight(func)Other collections also supportfold[4] andreduce.[5] There is alsoResult.fold(onSuccess, onFailure),[6] which reduces aResult<T> (either success or failure) to the return type ofonSuccess andonFailure.
LFE(lists:foldlfuncaccumlist)(lists:foldrfuncaccumlist)
Logtalkfold_left(Closure, Initial, List, Result)fold_right(Closure, Initial, List, Result)Meta-predicates provided by themeta standard library object. The abbreviationsfoldl andfoldr may also be used.
Maplefoldl(func,initval,sequence)foldr(func,initval,sequence)foldl(func,sequence)foldr(func,sequence)
MathematicaFold[func,initval,list]Fold[func,initval, Reverse[list]]Fold[func,list]Fold[func, Reverse[list]]NestWhileList[func,,initval,predicate]Fold without an initial value is supported in versions 10.0 and higher.
MATLABfold(@func,list,defaultVal)fold(@func, flip(list),defaultVal)fold(@func,list)fold(@func, flip(list))Requires Symbolic Math Toolbox, supported from R2016b.
Maximalreduce(func,list,initval)rreduce(func,list,initval)lreduce(func,list)rreduce(func,list)
OCamlList.fold_leftfuncinitvallist
Array.fold_leftfuncinitvalarray
List.fold_rightfunclistinitval
Array.fold_rightfuncarrayinitval
Oz{FoldLListFuncInitVal}{FoldRListFuncInitVal}
PARI/GPfold(f,A )
Perlreduceblockinitval,listreduceblocklistinList::Util module
PHParray_reduce(array,func,initval)array_reduce(array_reverse(array),func,initval)array_reduce(array,func)array_reduce(array_reverse(array),func)Wheninitval is not supplied, NULL is used, so this is not a true foldl1. Before PHP 5.3,initval can only be integer.func is acallbackArchived 2020-11-28 at theWayback Machine. Tryarray_reduce online.
Python 2.xreduce(func,list,initval)reduce(lambdax,y:func(y, x), reversed(list),initval)reduce(func,list)reduce(lambdax,y:func(y, x), reversed(list))
Python 3.xfunctools.reduce(func,list,initval)functools.reduce(lambdax,y:func(y, x), reversed(list),initval)functools.reduce(func,list)functools.reduce(lambdax,y:func(y, x), reversed(list))In modulefunctools.[7]
RReduce(func,list,initval)Reduce(func,list,initval, right=TRUE)Reduce(func,list)Reduce(func,list, right=TRUE)R supports right folding and left or right folding with or without an initial value through theright andinit arguments to theReduce function.
Racket(foldlfunc initvallist)(foldrfunc initvallist)
Rubyenum.inject(initval,&block)
enum.reduce(initval,&block)
enum.reverse_each.inject(initval,&block)
enum.reverse_each.reduce(initval,&block)
enum.inject(&block)
enum.reduce(&block)
enum.reverse_each.inject(&block)
enum.reverse_each.reduce(&block)
In Ruby 1.8.7+, can also pass a symbol representing a function instead of a block.
enum is an Enumeration
Please notice that these implementations of right folds are wrong for non-commutative&block (also initial value is put on wrong side).
Rustiterator.fold(initval,func)iterator.rev().fold(initval,func)iterator.reduce(func)iterator.rev().reduce(func)iterator.rev() requiresiterator to be aDoubleEndedIterator.[8]
Scalalist.foldLeft(initval)(func)
(initval /:list)(func)
list.foldRight(initval)(func)
(list :\initval)(func)
list.reduceLeft(func)list.reduceRight(func)Scala's symbolic fold syntax was intended to resemble the left- or right-leaning tree commonly used to explain the fold operation,[9] but has since been reinterpreted as an illustration of a toppling domino.[10] The colon comes from a general Scala syntax mechanism whereby the apparent infix operator is invoked as a method on the left operand with the right operand passed as an argument, or vice versa if the operator's last character is a colon, here applied symmetrically.

Scala also features the tree-like folds using the methodlist.fold(z)(op).[11]

Scheme R6RS(fold-leftfuncinitvallist)
(vector-foldfuncinitvalvector)
(fold-rightfuncinitvallist)
(vector-fold-rightfuncinitvalvector)
(reduce-leftfuncdefaultvallist)(reduce-rightfuncdefaultvallist)(unfoldpfgseed[tail-gen])
unfold-rightpfgseed[tail]
(vector-unfoldflengthinitial-seed···)
(vector-unfold-rightflengthinitial-seed···)
srfi/1 srfi/43
SmalltalkaCollection inject:aValue into:aBlockaCollection reduce:aBlockANSI Smalltalk doesn't define#reduce: but many implementations do.
Standard MLfoldlfuncinitvallist
Array.foldlfuncinitvalarray
foldrfuncinitvallist
Array.foldrfuncinitvalarray
The supplied function takes its arguments in a tuple. Forfoldl, the folding function takes arguments in the same order as forfoldr.
Swiftarray.reduce(initval,func)
reduce(sequence,initval,func)
array.reverse().reduce(initval,func)
XPathfold-left($input,$zero,$action)
array:fold-left($input,$zero,$action)
fold-right($input,$zero,$action)
array:fold-right($input,$zero,$action)
Two functions exist for each case because XPath offerssequences for unstructured andarrays for structured data.
Xtenditerable.fold(initval,[func])iterable.reduce[func]

Universality

[edit]

Fold is apolymorphic function. For anyg having a definition

g[]=vg(x:xs)=fx(gxs)

theng can be expressed as[12]

g=foldrfv

Also, in alazy language with infinite lists, afixed point combinator can be implemented via fold,[13] proving that iterations can be reduced to folds:

yf=foldr(\_->f)undefined(repeatundefined)

See also

[edit]

References

[edit]
  1. ^"Haskell unit 6: The higher-order fold functions | Antoni Diller".www.cantab.net. Retrieved2023-04-04.
  2. ^Richard Bird, "Pearls of Functional Algorithm Design", Cambridge University Press 2010,ISBN 978-0-521-51338-8, p. 42
  3. ^"Array.prototype.reduce() - JavaScript | MDN".developer.mozilla.org. 2023-12-11. Retrieved2024-01-16.
  4. ^"fold - Kotlin Programming Language".Kotlin. Jetbrains. Retrieved29 March 2019.
  5. ^"reduce - Kotlin Programming Language".Kotlin. Jetbrains. Retrieved29 March 2019.
  6. ^"Result - Kotlin Programming Language".Kotlin. Jetbrains. Retrieved29 March 2019.
  7. ^For referencefunctools.reduce:import functools
    For referencereduce:from functools import reduce
  8. ^"Iterator in core::iter".Rust. Rust Team. Retrieved2021-06-22.
  9. ^Odersky, Martin (2008-01-05)."Re: Blog: My verdict on the Scala language".Newsgroupcomp.scala.lang. Archived fromthe original on 14 May 2015. Retrieved14 October 2013.
  10. ^Sterling, Nicholas (28 July 2010)."An intuitive feel for Scala's /: operator (foldLeft)". Retrieved24 June 2016.
  11. ^"Fold API - Scala Standard Library".www.scala-lang.org. Retrieved2018-04-10.
  12. ^Hutton, Graham (1999)."A tutorial on the universality and expressiveness of fold"(PDF).Journal of Functional Programming.9 (4):355–372.doi:10.1017/S0956796899003500. RetrievedMarch 26, 2009.
  13. ^Pope, Bernie."Getting a Fix from the Right Fold"(PDF).The Monad.Reader (6):5–16. RetrievedMay 1, 2011.

External links

[edit]
Retrieved from "https://en.wikipedia.org/w/index.php?title=Fold_(higher-order_function)&oldid=1319528177"
Categories:
Hidden categories:

[8]ページ先頭

©2009-2026 Movatter.jp