Scott encoding
Representing 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.
The Church numeral 3 is : the whole iteration. The Scott numeral 3 is : one layer and the rest. Church's numeral is its own fold, so a match costs a full traversal, which is why its predecessor is linear. Scott's is its own match, so a fold needs recursion:
In OCaml the recursive type is a record with a polymorphic field, and the recursion comes from let rec.
(* A Scott numeral is its own case analysis: what to do for zero,and what to do with the predecessor. *)type nat = { case : 'r. 'r -> (nat -> 'r) -> 'r }let zero = { case = (fun z _ -> z) }let succ n = { case = (fun _ s -> s n) }let pred n = n.case zero (fun m -> m) (* one step, no iteration *)let is_zero n = n.case true (fun _ -> false)(* Anything that iterates needs recursion from outside the encoding. *)let rec add m n = m.case n (fun m' -> succ (add m' n))let to_int n =let rec go acc n = n.case acc (fun m -> go (acc + 1) m) ingo 0 nlet of_int k =let rec go acc k = if k = 0 then acc else go (succ acc) (k - 1) ingo zero k
pred on a numeral a million deep is one step.
pred 1000000 -> 999999add 2 3 -> 5is_zero (pred 1) -> true
The type is recursive, , so it cannot be written in System F without recursive types. Mogensen used the same encoding for λ-terms themselves, which gives a self-interpreter for the λ-calculus a few lines long.
see also
- 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.
- Y combinatorA 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.
- Algebraic data typeA type built from sums (a value is one of several cases) and products (a value has several fields at once), possibly recursively. The name comes from counting: the number of values of a sum is the sum of the counts, and of a product the product, so types obey the laws of algebra.
further reading
- [1]T. Æ. Mogensen, “Efficient self-interpretation in lambda calculus”, Journal of Functional Programming 2 (1992).
- [2]J. M. Jansen, “Programming in the λ-calculus: from Church to Scott and back”, in The Beauty of Functional Code, LNCS 8106 (2013).