Church encoding

In pure λ-calculus there are no numbers, no booleans, no structures — only functions. Church encoding shows that nothing else is needed: data are their own processing operations.

Let’s start with booleans. And notice how practically grounded everything is here. There is no truth or falsehood in itself: these notions make sense only when they presuppose strategies of action. Otherwise there is no point in truth and falsehood existing at all. Feel the formulas.

tru\displaystyle \mathbf{tru} =λt f. t,\displaystyle {} = \lambda t\,f.\,t,​fls\displaystyle \mathbf{fls}​=λt f. f\displaystyle {} = \lambda t\,f.\,f
(1.11)

Here tt and ff are simply two arguments (mnemonic: the “true” branch and the “false” branch): tru\mathbf{tru} returns the first, fls\mathbf{fls} the second. So a boolean value is itself a choice between two — a ready-made if\mathbf{if}.

if\displaystyle \mathbf{if} =λb t e. b t e,\displaystyle {} = \lambda b\,t\,e.\,b\,t\,e,​not\displaystyle \mathbf{not}​=λb. b fls tru\displaystyle {} = \lambda b.\,b\,\mathbf{fls}\,\mathbf{tru}
(1.12)

Here if\mathbf{if} does almost nothing on its own: it takes a boolean bb and hands it two branches tt and ee, and bb picks the right one. not\mathbf{not} is just as simple: it gives the boolean its branches in reverse order, so truth chooses falsehood and falsehood chooses truth.

and\displaystyle \mathbf{and} =λp q. p q p,\displaystyle {} = \lambda p\,q.\,p\,q\,p,​or\displaystyle \mathbf{or}​=λp q. p p q\displaystyle {} = \lambda p\,q.\,p\,p\,q
(1.13)

In and\mathbf{and} the first boolean pp decides what to return: if pp is true, the result depends on qq; if false, falsehood is returned at once. In or\mathbf{or} it is the opposite: if pp is true, truth can be returned immediately; if false, we have to look at qq.

Let’s substitute truth (11​=tru{} = \mathbf{tru}, 00​=fls{} = \mathbf{fls}) and perform the reductions:

not  tru  \displaystyle \mathbf{not}\;\mathbf{tru} \; →β  tru  fls  tru  \displaystyle {} \to_\beta\; \mathbf{tru}\;\mathbf{fls}\;\mathbf{tru} \;​↠β  fls(¬1=0)\displaystyle {} \twoheadrightarrow_\beta\; \mathbf{fls} \qquad (\lnot 1 = 0)
and  tru  tru  \displaystyle \mathbf{and}\;\mathbf{tru}\;\mathbf{tru} \; ↠β  tru  tru  tru  \displaystyle {} \twoheadrightarrow_\beta\; \mathbf{tru}\;\mathbf{tru}\;\mathbf{tru} \;​↠β  tru(1∧1=1)\displaystyle {} \twoheadrightarrow_\beta\; \mathbf{tru} \qquad (1 \land 1 = 1)
or  tru  tru  \displaystyle \mathbf{or}\;\mathbf{tru}\;\mathbf{tru} \; ↠β  tru  tru  tru  \displaystyle {} \twoheadrightarrow_\beta\; \mathbf{tru}\;\mathbf{tru}\;\mathbf{tru} \;​↠β  tru(1∨1=1)\displaystyle {} \twoheadrightarrow_\beta\; \mathbf{tru} \qquad (1 \lor 1 = 1)

Now let’s talk about pairs: a very important type, widely used in Haskell. A pair is a function that holds two values and waits for a “receiver”:

pair\displaystyle \mathbf{pair} =λx y f. f x y,\displaystyle {} = \lambda x\,y\,f.\,f\,x\,y,​fst\displaystyle \mathbf{fst}​=λp. p tru,\displaystyle {} = \lambda p.\,p\,\mathbf{tru},​snd\displaystyle \mathbf{snd}​=λp. p fls\displaystyle {} = \lambda p.\,p\,\mathbf{fls}
(1.14)

fst\mathbf{fst} hands the pair tru\mathbf{tru}, snd\mathbf{snd} hands it fls\mathbf{fls}, and the pair gives back the right component:

fst (pair  a  b)  \displaystyle \mathbf{fst}\,(\mathbf{pair}\;a\;b) \; ↠β  tru  a  b  \displaystyle {} \twoheadrightarrow_\beta\; \mathbf{tru}\;a\;b \;​↠β  a\displaystyle {} \twoheadrightarrow_\beta\; a
snd (pair  a  b)  \displaystyle \mathbf{snd}\,(\mathbf{pair}\;a\;b) \; ↠β  fls  a  b  \displaystyle {} \twoheadrightarrow_\beta\; \mathbf{fls}\;a\;b \;​↠β  b\displaystyle {} \twoheadrightarrow_\beta\; b

Notice how neat this is: fst\mathbf{fst} and snd\mathbf{snd} take a pair as their argument and hand control over to it — the pair itself then decides what to return. These are, so to speak, decorator functions.

Now for the most pressing thing — the natural numbers. A number nn is “apply a function nn times”:

n‾\displaystyle \overline{n} =λf x. f n(x):0‾\displaystyle {} = \lambda f\,x.\,f^{\,n}(x): \qquad \overline{0}​=λf x. x,\displaystyle {} = \lambda f\,x.\,x,​1‾\displaystyle \overline{1}​=λf x. f x,\displaystyle {} = \lambda f\,x.\,f\,x,​2‾\displaystyle \overline{2}​=λf x. f (f x)\displaystyle {} = \lambda f\,x.\,f\,(f\,x)
(1.15)

Important: ff and xx here are arbitrary — they are supplied by whoever uses the number. A numeral fixes only “how many times to apply”, not “what to apply”.

Feed the numeral a function ff and an argument xx — and it applies ff exactly as many times as the number specifies:

0‾  f  x  \displaystyle \overline{0}\;f\;x \; ↠β  x\displaystyle {} \twoheadrightarrow_\beta\; x
3‾  f  x  \displaystyle \overline{3}\;f\;x \; ↠β  f (f (f x))\displaystyle {} \twoheadrightarrow_\beta\; f\,(f\,(f\,x))

Now let’s learn to compute — all arithmetic grows from one trick: “apply ff the required number of times”.

Let’s start with adding one: succ\mathbf{succ} tacks one more application of ff onto the number:

succ\displaystyle \mathbf{succ} =λn f x. f (n f x)\displaystyle {} = \lambda n\,f\,x.\,f\,(n\,f\,x)
(1.16)
succ  n‾  \displaystyle \mathbf{succ}\;\overline{n} \; →β  λf x. f (n‾ f x)  \displaystyle {} \to_\beta\; \lambda f\,x.\,f\,(\overline{n}\,f\,x) \;​↠β  λf x. f(f n(x))  \displaystyle {} \twoheadrightarrow_\beta\; \lambda f\,x.\,f\bigl(f^{\,n}(x)\bigr) \;​=  λf x. f n+1(x)  \displaystyle {} =\; \lambda f\,x.\,f^{\,n+1}(x) \;​=  n+1‾\displaystyle {} =\; \overline{n+1}

Addition continues the idea — it applies ff first n‾\overline{n} times, then m‾\overline{m} more:

add\displaystyle \mathbf{add} =λm n f x. m f (n f x)\displaystyle {} = \lambda m\,n\,f\,x.\,m\,f\,(n\,f\,x)
(1.17)
add  m‾  n‾  \displaystyle \mathbf{add}\;\overline{m}\;\overline{n} \; ↠β  λf x. m‾ f (n‾ f x)  \displaystyle {} \twoheadrightarrow_\beta\; \lambda f\,x.\,\overline{m}\,f\,(\overline{n}\,f\,x) \;​↠β  λf x. f m(f n(x))  \displaystyle {} \twoheadrightarrow_\beta\; \lambda f\,x.\,f^{\,m}\bigl(f^{\,n}(x)\bigr) \;​=  m+n‾\displaystyle {} =\; \overline{m+n}

Multiplication is already repeated addition:

mul\displaystyle \mathbf{mul} =λm n f. m (n f)\displaystyle {} = \lambda m\,n\,f.\,m\,(n\,f)
(1.18)
mul  m‾  n‾  \displaystyle \mathbf{mul}\;\overline{m}\;\overline{n} \; ↠β  λf. m‾ (n‾ f)  \displaystyle {} \twoheadrightarrow_\beta\; \lambda f.\,\overline{m}\,(\overline{n}\,f) \;​↠β  λf x. (f n)m(x)  \displaystyle {} \twoheadrightarrow_\beta\; \lambda f\,x.\,\bigl(f^{\,n}\bigr)^{m}(x) \;​=  λf x. f mn(x)  \displaystyle {} =\; \lambda f\,x.\,f^{\,mn}(x) \;​=  mn‾\displaystyle {} =\; \overline{mn}

Exponentiation is repeated multiplication, and its notation is very short:

pow\displaystyle \mathbf{pow} =λm n. n m\displaystyle {} = \lambda m\,n.\,n\,m
(1.19)
pow  m‾  n‾  \displaystyle \mathbf{pow}\;\overline{m}\;\overline{n} \; →β  n‾ m‾  \displaystyle {} \to_\beta\; \overline{n}\,\overline{m} \;​↠β  m n‾\displaystyle {} \twoheadrightarrow_\beta\; \overline{m^{\,n}}

In pow\mathbf{pow} the arguments simply swap places: n‾ m‾\overline{n}\,\overline{m} is m‾\overline{m} applied as a function nn times, which yields m n‾\overline{m^{\,n}}.

Subtraction is surprisingly hard: pred\mathbf{pred} is built through pairs — we run a pair (i−1, i)(i-1,\,i) along the number and take the first component:

pred\displaystyle \mathbf{pred} =λn. fst (n  (λp. pair (snd p) (succ (snd p)))  (pair 0‾ 0‾))\displaystyle {} = \lambda n.\,\mathbf{fst}\,\bigl(n\;(\lambda p.\,\mathbf{pair}\,(\mathbf{snd}\,p)\,(\mathbf{succ}\,(\mathbf{snd}\,p)))\;(\mathbf{pair}\,\overline{0}\,\overline{0})\bigr)
(1.20)

By a well-known legend, Stephen Kleene came up with the predecessor function (pred\mathbf{pred}, predecessor) — the key to subtracting Church numerals — right in the dentist’s chair.

Everything “subtractive” rests on pred\mathbf{pred}: subtraction itself is repeated pred\mathbf{pred}, and division is repeated subtraction. We give no separate λ-definition for division: there is no new idea in it — the same pred\mathbf{pred} in a loop, only a bulkier expression.

Let’s check pred\mathbf{pred} on a general numeral n‾\overline{n}. Denote the step σ\sigma​=λp. pair (snd p) (succ (snd p)){} = \lambda p.\,\mathbf{pair}\,(\mathbf{snd}\,p)\,(\mathbf{succ}\,(\mathbf{snd}\,p)) — it shifts the pair: it drops the first component and grows the second:

σ (pair  a  b)  \displaystyle \sigma\,(\mathbf{pair}\;a\;b) \; ↠β  pair  b  (succ  b)\displaystyle {} \twoheadrightarrow_\beta\; \mathbf{pair}\;b\;(\mathbf{succ}\;b)

Substitute n‾\overline{n} into the definition — that means applying σ\sigma exactly nn times to the starting pair pair 0‾ 0‾\mathbf{pair}\,\overline{0}\,\overline{0}:

pred  n‾  \displaystyle \mathbf{pred}\;\overline{n} \; →β  fst (n‾ σ (pair 0‾ 0‾))  \displaystyle {} \to_\beta\; \mathbf{fst}\,\bigl(\overline{n}\,\sigma\,(\mathbf{pair}\,\overline{0}\,\overline{0})\bigr) \;​↠β  fst (σ n(pair 0‾ 0‾))\displaystyle {} \twoheadrightarrow_\beta\; \mathbf{fst}\,\bigl(\sigma^{\,n}(\mathbf{pair}\,\overline{0}\,\overline{0})\bigr)

The pairs run along a chain — the second component counts the steps, the first lags by one:

pair 0‾ 0‾  \displaystyle \mathbf{pair}\,\overline{0}\,\overline{0} \; →  pair 0‾ 1‾  \displaystyle {} \to\; \mathbf{pair}\,\overline{0}\,\overline{1} \;​→  pair 1‾ 2‾  \displaystyle {} \to\; \mathbf{pair}\,\overline{1}\,\overline{2} \;​→  ⋯  \displaystyle {} \to\; \cdots \;​→  pair n−1‾ n‾\displaystyle {} \to\; \mathbf{pair}\,\overline{n-1}\,\overline{n}

It only remains to take the first component:

pred  n‾  \displaystyle \mathbf{pred}\;\overline{n} \; ↠β  fst (pair n−1‾ n‾)  \displaystyle {} \twoheadrightarrow_\beta\; \mathbf{fst}\,(\mathbf{pair}\,\overline{n-1}\,\overline{n}) \;​↠β  n−1‾\displaystyle {} \twoheadrightarrow_\beta\; \overline{n-1}

For 0‾\overline{0} the chain does not move, so pred 0‾\mathbf{pred}\,\overline{0}​↠β0‾{} \twoheadrightarrow_\beta \overline{0} — we take the predecessor of zero to be zero.

iszero\displaystyle \mathbf{iszero} =λn. n (λz. fls) tru\displaystyle {} = \lambda n.\,n\,(\lambda z.\,\mathbf{fls})\,\mathbf{tru}
(1.21)

iszero\mathbf{iszero} feeds the number a function that answers fls\mathbf{fls} at every step, with the start value tru\mathbf{tru}: zero has no steps — tru\mathbf{tru} remains; any other number has at least one step, which turns it into fls\mathbf{fls}.

A list is its own right fold (the fold encoding):

[x1,…,xn]  \displaystyle [x_1, \dots, x_n] \; ⇝  λc n. c x1 (c x2 (⋯(c xn n)))\displaystyle {} \rightsquigarrow\; \lambda c\,n.\,c\,x_1\,(c\,x_2\,(\cdots(c\,x_n\,n)))
(1.22)

The same trick in Haskell — the type of foldr\mathtt{foldr}: a list is fully determined by how it folds. A Church numeral is “a list of nn identical applications”.

-- a list is built by the constructor (:) (cons) and terminated by [] (nil):
[1, 2, 3]  ==  1 : (2 : (3 : []))   -- True

-- Church encoding is the same chain, only (:) and [] become parameters;
-- in Haskell this is exactly foldr:
foldr (:) []  [1, 2, 3]   -- 1 : (2 : (3 : [])) = [1,2,3]   (rebuilt the list)
foldr (+) 0   [1, 2, 3]   -- 1 + (2 + (3 + 0))  = 6         (folded to a sum)

But surely there’s normal programming, with numbers and even floating point. Why cook all this up? I wanted declarative programming, where everything is simple, and instead… Actually the answer is simple: folks, if you don’t like the circus, there’s nothing for you here, don’t come; but if you love the circus…