In combinatory logic for computer science, a fixed-point combinator (or fixpoint combinator),[1] is a higher-order function (i.e. a function which takes a function as argument) that returns some fixed point (a value that is mapped to itself) of its argument function, if one exists.
Formally, if
rm{fix}
f
rm{fix} f
f (rm{fix} f)=rm{fix} f .
Fixed-point combinators can be defined in the lambda calculus and in functional programming languages and provide a means to allow for recursive definitions.
Y combinator in lambda calculusrm{fix}
Y=λf. (λx.f (x x)) (λx.f (x x))
λx.f (x x)
f (x x)
(x x)
The following calculation verifies that
Yg
g
Y g | =(λf.(λx.f (x x)) (λx.f (x x))) g | by the definition of Y | |
=(λx.g (x x)) (λx.g (x x)) | by β-reduction: replacing the formal argument f of Y with the actual argument g | ||
=g ((λx.g (x x)) (λx.g (x x))) | by β-reduction: replacing the formal argument x of the first function with the actual argument (λx.g (x x)) | ||
=g (Y g) | by second equality, above |
The lambda term
g (Y g)
Y g
Applied to a function with one variable, the Y combinator usually does not terminate. More interesting results are obtained by applying the Y combinator to functions of two or more variables. The additional variables may be used as a counter, or index. The resulting function behaves like a while or a for loop in an imperative language.
Used in this way, the Y combinator implements simple recursion. The lambda calculus does not allow a function to appear as a term in its own definition as is possible in many programming languages, but a function can be passed as an argument to a higher-order function that applies it in a recursive manner.
The Y combinator may also be used in implementing Curry's paradox. The heart of Curry's paradox is that untyped lambda calculus is unsound as a deductive system, and the Y combinator demonstrates this by allowing an anonymous expression to represent zero, or even many values. This is inconsistent in mathematical logic.
An example implementation of the Y combinator in two languages is presented below.
Y = lambda f: (lambda x: f(x(x)))(lambda x: f(x(x)))
Y(Y)
int main
Note that both of these programs, while formally correct, are useless in practice; they both loop indefinitely until they terminate through stack overflow. More generally, as both Python and C++ use strict evaluation, the Y combinator is generally useless in those languages; see below for the Z combinator, which can be used in strict programming languages.
The Y combinator is an implementation of a fixed-point combinator in lambda calculus. Fixed-point combinators may also be easily defined in other functional and imperative languages. The implementation in lambda calculus is more difficult due to limitations in lambda calculus.The fixed-point combinator may be used in a number of different areas:
Fixed-point combinators may be applied to a range of different functions, but normally will not terminate unless there is an extra parameter. When the function to be fixed refers to its parameter, another call to the function is invoked, so the calculation never gets started. Instead, the extra parameter is used to trigger the start of the calculation.
The type of the fixed point is the return type of the function being fixed. This may be a real or a function or any other type.
In the untyped lambda calculus, the function to apply the fixed-point combinator to may be expressed using an encoding, like Church encoding. In this case particular lambda terms (which define functions) are considered as values. "Running" (beta reducing) the fixed-point combinator on the encoding gives a lambda term for the result which may then be interpreted as fixed-point value.
Alternately, a function may be considered as a lambda term defined purely in lambda calculus.
These different approaches affect how a mathematician and a programmer may regard a fixed-point combinator. A mathematician may see the Y combinator applied to a function as being an expression satisfying the fixed-point equation, and therefore a solution.
In contrast, a person only wanting to apply a fixed-point combinator to some general programming task may see it only as a means of implementing recursion.
Many functions do not have any fixed points, for instance
f:\N\to\N
f(n)=n+1
The fixed-point combinator may be defined in mathematics and then implemented in other languages. General mathematics defines a function based on its extensional properties.[4] That is, two functions are equal if they perform the same mapping. Lambda calculus and programming languages regard function identity as an intensional property. A function's identity is based on its implementation.
A lambda calculus function (or term) is an implementation of a mathematical function. In the lambda calculus there are a number of combinators (implementations) that satisfy the mathematical definition of a fixed-point combinator.
Combinatory logic is a higher-order functions theory. A combinator is a closed lambda expression, meaning that it has no free variables. The combinators may be combined to direct values to their correct places in the expression without ever naming them as variables.
Fixed-point combinators can be used to implement recursive definition of functions. However, they are rarely used in practical programming.[5] Strongly normalizing type systems such as the simply typed lambda calculus disallow non-termination and hence fixed-point combinators often cannot be assigned a type or require complex type system features. Furthermore fixed-point combinators are often inefficient compared to other strategies for implementing recursion, as they require more function reductions and construct and take apart a tuple for each group of mutually recursive definitions.
The factorial function provides a good example of how a fixed-point combinator may be used to define recursive functions. The standard recursive definition of the factorial function in mathematics can be written as
\operatorname{fact} n=\begin{cases} 1&if~n=0\\ n x \operatorname{fact}(n-1)&otherwise. \end{cases}
where n is a non-negative integer.If we want to implement this in lambda calculus, where integers are represented using Church encoding, we run into the problem that the lambda calculus does not allow the name of a function ('fact') to be used in the function's definition. This can be circumvented using a fixed-point combinator
sf{fix}
Define a function F of two arguments f and n:
F f n=(\operatorname{IsZero} n) 1 (\operatorname{multiply} n (f (\operatorname{pred} n)))
(\operatorname{IsZero} n)
\operatorname{pred} n
Now define
\operatorname{fact}=sf{fix} F
\operatorname{fact}
\begin{align}\operatorname{fact}n&=F \operatorname{fact} n\\ &=(\operatorname{IsZero} n) 1 (\operatorname{multiply} n (\operatorname{fact} (\operatorname{pred} n))) \end{align}
as desired.
The Y combinator, discovered by Haskell B. Curry, is defined as
Y=λf.(λx.f (x x)) (λx.f (x x))
In untyped lambda calculus fixed-point combinators are not especially rare. In fact there are infinitely many of them.[6] In 2005 Mayer Goldberg showed that the set of fixed-point combinators of untyped lambda calculus is recursively enumerable.[7]
The Y combinator can be expressed in the SKI-calculus as
Y=S(K(SII))(S(S(KS)K)(K(SII)))
Additional combinators (B, C, K, W system) allow for a much shorter definition. With
U=SII
S(Kx)yz=x(yz)=Bxyz
Sx(Ky)z=xzy=Cxyz
Y=S(KU)(SB(KU))=BU(CBU)
The simplest fixed-point combinator in the SK-calculus, found by John Tromp, is
Y'=SSK(S(K(SS(S(SSK))))K)
although note that it is not in normal form, which is longer. This combinator corresponds to the lambda expression
Y'=(λxy.xyx)(λyx.y(xyx))
The following fixed-point combinator is simpler than the Y combinator, and β-reduces into the Y combinator; it is sometimes cited as the Y combinator itself:
X=λf.(λx.xx)(λx.f(xx))
Another common fixed-point combinator is the Turing fixed-point combinator (named after its discoverer, Alan Turing):[8] [2]
\Theta=(λxy.y(xxy)) (λxy.y(xxy))
Its advantage over
Y
\Theta f
f (\Thetaf)
Y f
f (Yf)
\Theta
\Thetav=(λxy.y(λz.xxyz)) (λxy.y(λz.xxyz))
The analog for mutual recursion is a polyvariadic fix-point combinator,[9] [10] [11] which may be denoted Y*.
Strict fixed-point combinatorIn a strict programming language the Y combinator will expand until stack overflow, or never halt in case of tail call optimization.[12] The Z combinator will work in strict languages (also called eager languages, where applicative evaluation order is applied). The Z combinator has the next argument defined explicitly, preventing the expansion of
Zg
Zgv=g(Zg)v .
and in lambda calculus it is an eta-expansion of the Y combinator:
Z=λf.(λx.f(λv.xxv)) (λx.f(λv.xxv)) .
If F is a fixed-point combinator in untyped lambda calculus, then we have
F=λx.Fx=λx.x(Fx)=λx.x(x(Fx))= …
Terms that have the same Böhm tree as a fixed-point combinator, i.e. have the same infinite extension
λx.x(x(x … ))
N=BM(B(BM)B)
B=λxyz.x(yz)
M=λx.xx .
The Y combinator is a particular implementation of a fixed-point combinator in lambda calculus. Its structure is determined by the limitations of lambda calculus. It is not necessary or helpful to use this structure in implementing the fixed-point combinator in other languages.
Simple examples of fixed-point combinators implemented in some programming paradigms are given below.
In a language that supports lazy evaluation, like in Haskell, it is possible to define a fixed-point combinator using the defining equation of the fixed-point combinator which is conventionally named fix
. Since Haskell has lazy datatypes, this combinator can also be used to define fixed points of data constructors (and not only to implement recursive functions). The definition is given here, followed by some usage examples. In Hackage, the original sample is: [14]
fix (\x -> 9) -- this evaluates to 9
fix (\x -> 3:x) -- evaluates to the lazy infinite list [3,3,3,...]
fact = fix fac -- evaluates to the factorial function where fac f 0 = 1 fac f x = x * f (x-1)
fact 5 -- evaluates to 120
In a strict functional language, as illustrated below with OCaml, the argument to f is expanded beforehand, yielding an infinite call sequence,
f (f...(f (fix f))...) x
This may be resolved by defining fix with an extra parameter.
let factabs fact = function (* factabs has extra level of lambda abstraction *) 0 -> 1 | x -> x * fact (x-1)
let _ = (fix factabs) 5 (* evaluates to "120" *)
In a multi-paradigm functional language (one decorated with imperative features), such as Lisp, Peter Landin suggested the use of a variable assignment to create a fixed-point combinator,[15] as in the below example using Scheme:
Using a lambda calculus with axioms for assignment statements, it can be shown that Y!
satisfies the same fixed-point law as the call-by-value Y combinator:[16] [17]
(Y! λx.e)e'=(λx.e) (Y! λx.e)e'
In more idiomatic modern Lisp usage, this would typically be handled via a lexically scoped label (a let
expression), as lexical scope was not introduced to Lisp until the 1970s:
Or without the internal label:
This example is a slightly interpretive implementation of a fixed-point combinator. A class is used to contain the fix function, called fixer. The function to be fixed is contained in a class that inherits from fixer. The fix function accesses the function to be fixed as a virtual function. As for the strict functional definition, fix is explicitly given an extra parameter x, which means that lazy evaluation is not needed.
class fact : public fixer
long result = fact.fix(5);
In System F (polymorphic lambda calculus) a polymorphic fixed-point combinator has type[18]
∀a.(a → a) → awhere a is a type variable. That is, fix takes a function, which maps a → a and uses it to return a value of type a.
In the simply typed lambda calculus extended with recursive data types, fixed-point operators can be written, but the type of a "useful" fixed-point operator (one whose application always returns) may be restricted.
In the simply typed lambda calculus, the fixed-point combinator Y cannot be assigned a type[19] because at some point it would deal with the self-application sub-term
x~x
{\Gamma\vdashx:t1\tot2 \Gamma\vdashx:t1}\over{\Gamma\vdashx~x:t2}
where
x
t1=t1\tot2
In programming languages that support recursive data types, it is possible to type the Y combinator by appropriately accounting for the recursion at the type level. The need to self-apply the variable x can be managed using a type (Rec a
), which is defined so as to be isomorphic to (Rec a -> a
).
For example, in the following Haskell code, we have In
and out
being the names of the two directions of the isomorphism, with types:[20] [21]
which lets us write:
y :: (a -> a) -> ay = \f -> (\x -> f (out x x)) (In (\x -> f (out x x)))
Or equivalently in OCaml:
let y f = (fun x a -> f (out x x) a) (In (fun x a -> f (out x x) a))
Alternatively:
Because fixed-point combinators can be used to implement recursion, it is possible to use them to describe specific types of recursive computations, such as those in fixed-point iteration, iterative methods, recursive join in relational databases, data-flow analysis, FIRST and FOLLOW sets of non-terminals in a context-free grammar, transitive closure, and other types of closure operations.
A function for which every input is a fixed point is called an identity function. Formally:
\forallx(f x=x)
x
f
f
Other functions have the special property that, after being applied once, further applications don't have any effect. More formally:
\forallx(f (f x)=f x)
In lambda calculus, from a computational point of view, applying a fixed-point combinator to an identity function or an idempotent function typically results in non-terminating computation. For example, we obtain
(Y λx.x)=(λx.(xx) λx.(xx))
Fixed-point combinators do not necessarily exist in more restrictive models of computation. For instance, they do not exist in simply typed lambda calculus.
The Y combinator allows recursion to be defined as a set of rewrite rules,[22] without requiring native recursion support in the language.[23]
In programming languages that support anonymous functions, fixed-point combinators allow the definition and use of anonymous recursive functions, i.e. without having to bind such functions to identifiers. In this setting, the use of fixed-point combinators is sometimes called anonymous recursion.[24] [25]
p
λ
K