Y combinator
A lambda term Y with Y f = f (Y f) for every f, which gives recursion to a language with no named definitions. Under call-by-value it loops before f is ever called; the Z combinator puts the self-application behind a lambda and works in strict languages.
Two β-steps show that it is a fixed-point combinator:
Recursion then needs no names. Factorial is applied to a function that receives factorial as its first argument:
Under call-by-value an argument is evaluated before the call. In the argument is again, so evaluation unfolds without ever entering . The Z combinator η-expands the self-application:
is a value, so it is passed to unevaluated, and the self-application happens only when calls it with an argument.
In OCaml. x x has no simple type, so self-application goes through a recursive datatype; with that in place, Y and Z differ by one η-expansion.
(* Self-application needs a recursive type: a value that can be applied to itself. *)type 'a fix = Fix of ('a fix -> 'a)(* Y: fine under call-by-name, loops under call-by-value. *)let y f = let g (Fix x as fx) = f (x fx) in g (Fix g)(* Z: the self-application is under a lambda, so it waits for an argument. *)let z f = let g (Fix x as fx) = f (fun v -> x fx v) in g (Fix g)let fact = z (fun self n -> if n = 0 then 1 else n * self (n - 1))let fib = z (fun self n -> if n < 2 then n else self (n - 1) + self (n - 2))
Running it.
fact 10 -> 3628800fib 20 -> 6765y ... 10 -> exception Stack_overflow
No fixed-point combinator has a type in the simply typed λ-calculus, where every term normalizes. Typed languages add recursion as a primitive instead, let rec or a fix constant. Read as logic, the same term is Curry's paradox: with an unrestricted fixed point every proposition has a proof.
see also
- SKI combinatorsThree combinators, S x y z = x z (y z), K x y = x and I x = x, from which every closed lambda term can be built by application alone, with no variables. I is redundant, since S K K x = x. The translation from lambda terms is called bracket abstraction.
- Church encodingRepresenting data as functions in the pure lambda calculus: the numeral n applies its argument n times, true and false choose between two arguments, and a pair waits for a selector. Addition, multiplication and exponentiation are one line each; the predecessor needs a trick and takes time linear in n.
- Scott encodingRepresenting data by its case analysis rather than its fold: a Scott numeral takes what to do for zero and what to do with the predecessor. Pattern matching and the predecessor take one step; iteration needs recursion from outside, such as a fixed-point combinator.
- De Bruijn indicesA representation of lambda terms without variable names: each variable is the number of binders between it and the lambda that binds it. Alpha-equivalent terms become identical and substitution cannot capture a variable, at the price of shifting indices when a term moves under a binder.
further reading
- [1]H. B. Curry, R. Feys, Combinatory Logic, Vol. I, North-Holland (1958).
- [2]G. D. Plotkin, “Call-by-name, call-by-value and the λ-calculus”, Theoretical Computer Science 1 (1975).
- [3]H. P. Barendregt, The Lambda Calculus: Its Syntax and Semantics, North-Holland (revised ed., 1984).