Lably
Skip to content

Fixed-point combinator

Higher-order function which returns some fixed point of the input function

  • 35 min read
  • 7 chapters

Related on Lably

Through Computer science, Function (mathematics) and Fixed point (mathematics)

Formally, if fix is a fixed-point combinator and the function f has one or more fixed points, then fixf is one of these fixed points, i.e.,

fixf=f(fixf).

Fixed-point combinators can be defined in the lambda calculus and in functional programming languages, and provide a means to allow for recursive definitions.

Introduction

Applied to a non-constant function of one variable that treats its argument as a piece of data (such as e.g. the sine function), the Y combinator usually does not terminate. Y really is meant to be used with codata, e.g. a colist constructor that places the first element there and expects "the rest of elements" as an argument, that will be fleshed out later; or a higher-order function that expects to be supplied as its first argument with a function "to compute the rest," which it might or might not call, as needed.

Applied to such "one step functional," Y arranges for the creation of that "rest of computation" function, consisting of that same step it was given and the "rest of computation" again, which really means just the original step repeated as many times as needed, calling "the rest" function as and if needed, but not always. The resulting function behaves like a while or a for loop. Used in this way, the Y combinator implements general recursion.

The lambda calculus does not have global names, for a function to refer to itself by name inside its own definition, as is possible in many programming languages. But it has local names, i.e. parameters in lambda abstractions. An abstraction term can receive another term as an argument, and refer to that argument by the parameter name inside the function's body.

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.

Y combinator in lambda calculus

In the classical untyped lambda calculus, every function has a fixed point. A particular implementation of fix is Haskell Curry's paradoxical combinator Y, given by

Y=λf.(λx.f(xx))(λx.f(xx))

(Here using the standard notations and conventions of lambda calculus: Y is a function that takes one argument f and returns the entire expression following the first period; the expression λx.f(xx) denotes a function that takes one argument x, thought of as a function, and returns the expression f(xx), where (xx) denotes x applied to itself. Juxtaposition of expressions denotes function application, is left-associative, and has higher precedence than the period.)

Verification

The following calculation verifies that Yg is indeed a fixed point of the function g:

The lambda term g(Yg) may not, in general, β-reduce to the term Yg. However, both terms β-reduce to the same term, as shown.

Example implementations

An example implementation of Y in the language R is presented below:

<span id="mwcg"></span><span class="n" id="mwcw">Y</span><span class="w" id="mwdA"> </span><span class="o" id="mwdQ"><-</span><span class="w" id="mwdg"> </span>\<span class="p" id="mwdw">(</span><span class="n" id="mweA">f</span><span class="p" id="mweQ">)</span><span class="w" id="mweg"> </span><span class="p" id="mwew">{</span> <span class="w" id="mwfA"> </span><span class="n" id="mwfQ">g</span><span class="w" id="mwfg"> </span><span class="o" id="mwfw"><-</span><span class="w" id="mwgA"> </span>\<span class="p" id="mwgQ">(</span><span class="n" id="mwgg">x</span><span class="p" id="mwgw">)</span><span class="w" id="mwhA"> </span><span class="nf" id="mwhQ">f</span><span class="p" id="mwhg">(</span><span class="nf" id="mwhw">x</span><span class="p" id="mwiA">(</span><span class="n" id="mwiQ">x</span><span class="p" id="mwig">))</span> <span class="w" id="mwiw"> </span><span class="nf" id="mwjA">g</span><span class="p" id="mwjQ">(</span><span class="n" id="mwjg">g</span><span class="p" id="mwjw">)</span> <span class="p" id="mwkA">}</span>

This can then be used to implement factorial as follows:

<span id="mwlA"></span><span class="n" id="mwlQ">fact</span><span class="w" id="mwlg"> </span><span class="o" id="mwlw"><-</span><span class="w" id="mwmA"> </span>\<span class="p" id="mwmQ">(</span><span class="n" id="mwmg">f</span><span class="p" id="mwmw">)</span><span class="w" id="mwnA"> </span>\<span class="p" id="mwnQ">(</span><span class="n" id="mwng">n</span><span class="p" id="mwnw">)</span> <span class="w" id="mwoA"> </span><span class="kr" id="mwoQ">if</span><span class="w" id="mwog"> </span><span class="p" id="mwow">(</span><span class="n" id="mwpA">n</span><span class="w" id="mwpQ"> </span><span class="o" id="mwpg">==</span><span class="w" id="mwpw"> </span><span class="m" id="mwqA">0</span><span class="p" id="mwqQ">)</span><span class="w" id="mwqg"> </span><span class="m" id="mwqw">1</span><span class="w" id="mwrA"> </span><span class="kr" id="mwrQ">else</span><span class="w" id="mwrg"> </span><span class="n" id="mwrw">n</span><span class="w" id="mwsA"> </span><span class="o" id="mwsQ">*</span><span class="w" id="mwsg"> </span><span class="nf" id="mwsw">f</span><span class="p" id="mwtA">(</span><span class="n" id="mwtQ">n</span><span class="w" id="mwtg"> </span><span class="o" id="mwtw">-</span><span class="w" id="mwuA"> </span><span class="m" id="mwuQ">1</span><span class="p" id="mwug">)</span> <span class="w" id="mwuw"> </span> <span class="nf" id="mwvA">Y</span><span class="p" id="mwvQ">(</span><span class="n" id="mwvg">fact</span><span class="p" id="mwvw">)(</span><span class="m" id="mwwA">5</span><span class="p" id="mwwQ">)</span><span class="w" id="mwwg"> </span><span class="c1" id="mwww"># yields 5! = 120</span>

Y is only needed when function names are absent. Substituting all the definitions into one line so that function names are not required gives:

<span id="mwxw"></span><span class="p" id="mwyA">(</span>\<span class="p" id="mwyQ">(</span><span class="n" id="mwyg">f</span><span class="p" id="mwyw">)</span><span class="w" id="mwzA"> </span><span class="p" id="mwzQ">(</span>\<span class="p" id="mwzg">(</span><span class="n" id="mwzw">x</span><span class="p" id="mw0A">)</span><span class="w" id="mw0Q"> </span><span class="nf" id="mw0g">f</span><span class="p" id="mw0w">(</span><span class="nf" id="mw1A">x</span><span class="p" id="mw1Q">(</span><span class="n" id="mw1g">x</span><span class="p" id="mw1w">)))(</span>\<span class="p" id="mw2A">(</span><span class="n" id="mw2Q">x</span><span class="p" id="mw2g">)</span><span class="w" id="mw2w"> </span><span class="nf" id="mw3A">f</span><span class="p" id="mw3Q">(</span><span class="nf" id="mw3g">x</span><span class="p" id="mw3w">(</span><span class="n" id="mw4A">x</span><span class="p" id="mw4Q">))))</span><span class="w" id="mw4g"> </span><span class="p" id="mw4w">(</span>\<span class="p" id="mw5A">(</span><span class="n" id="mw5Q">f</span><span class="p" id="mw5g">)</span><span class="w" id="mw5w"> </span>\<span class="p" id="mw6A">(</span><span class="n" id="mw6Q">n</span><span class="p" id="mw6g">)</span><span class="w" id="mw6w"> </span><span class="kr" id="mw7A">if</span><span class="w" id="mw7Q"> </span><span class="p" id="mw7g">(</span><span class="n" id="mw7w">n</span><span class="w" id="mw8A"> </span><span class="o" id="mw8Q">==</span><span class="w" id="mw8g"> </span><span class="m" id="mw8w">0</span><span class="p" id="mw9A">)</span><span class="w" id="mw9Q"> </span><span class="m" id="mw9g">1</span><span class="w" id="mw9w"> </span><span class="kr" id="mw-A">else</span><span class="w" id="mw-Q"> </span><span class="n" id="mw-g">n</span><span class="w" id="mw-w"> </span><span class="o" id="mw_A">*</span><span class="w" id="mw_Q"> </span><span class="nf" id="mw_g">f</span><span class="p" id="mw_w">(</span><span class="n" id="mwAQA">n</span><span class="w" id="mwAQE"> </span><span class="o" id="mwAQI">-</span><span class="w" id="mwAQM"> </span><span class="m" id="mwAQQ">1</span><span class="p" id="mwAQU">))</span><span class="w" id="mwAQY"> </span><span class="p" id="mwAQc">(</span><span class="m" id="mwAQg">5</span><span class="p" id="mwAQk">)</span>

This works because R uses lazy evaluation.

Languages that use strict evaluation, such as Python, C++, and other strict programming languages, can often express Y; however, any implementation is useless in practice since it loops indefinitely until terminating via a stack overflow.

Sign in to keep reading

6 more chapters are free with a Lably account.

More in Computing and communication