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} =λtf.t,fls\displaystyle = \lambda t\,f.\,t, \qquad \mathbf{fls} =λtf.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} =λbte.bte,not\displaystyle = \lambda b\,t\,e.\,b\,t\,e, \qquad \mathbf{not} =λb.bflstru\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} =λpq.pqp,or\displaystyle = \lambda p\,q.\,p\,q\,p, \qquad \mathbf{or} =λpq.ppq\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(11=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(11=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} =λxyf.fxy,fst\displaystyle = \lambda x\,y\,f.\,f\,x\,y, \qquad \mathbf{fst} =λp.ptru,snd\displaystyle = \lambda p.\,p\,\mathbf{tru}, \qquad \mathbf{snd} =λp.pfls\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} =λfx.fn(x):0\displaystyle = \lambda f\,x.\,f^{\,n}(x): \qquad \overline{0} =λfx.x,1\displaystyle = \lambda f\,x.\,x, \quad \overline{1} =λfx.fx,2\displaystyle = \lambda f\,x.\,f\,x, \quad \overline{2} =λfx.f(fx)\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(fx))\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} =λnfx.f(nfx)\displaystyle = \lambda n\,f\,x.\,f\,(n\,f\,x)
(1.16)
succ  n  \displaystyle \mathbf{succ}\;\overline{n} \; β  λfx.f(nfx)  \displaystyle \to_\beta\; \lambda f\,x.\,f\,(\overline{n}\,f\,x) \; β  λfx.f(fn(x))  \displaystyle \twoheadrightarrow_\beta\; \lambda f\,x.\,f\bigl(f^{\,n}(x)\bigr) \; =  λfx.fn+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} =λmnfx.mf(nfx)\displaystyle = \lambda m\,n\,f\,x.\,m\,f\,(n\,f\,x)
(1.17)
add  m  n  \displaystyle \mathbf{add}\;\overline{m}\;\overline{n} \; β  λfx.mf(nfx)  \displaystyle \twoheadrightarrow_\beta\; \lambda f\,x.\,\overline{m}\,f\,(\overline{n}\,f\,x) \; β  λfx.fm(fn(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} =λmnf.m(nf)\displaystyle = \lambda m\,n\,f.\,m\,(n\,f)
(1.18)
mul  m  n  \displaystyle \mathbf{mul}\;\overline{m}\;\overline{n} \; β  λf.m(nf)  \displaystyle \twoheadrightarrow_\beta\; \lambda f.\,\overline{m}\,(\overline{n}\,f) \; β  λfx.(fn)m(x)  \displaystyle \twoheadrightarrow_\beta\; \lambda f\,x.\,\bigl(f^{\,n}\bigr)^{m}(x) \; =  λfx.fmn(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} =λmn.nm\displaystyle = \lambda m\,n.\,n\,m
(1.19)
pow  m  n  \displaystyle \mathbf{pow}\;\overline{m}\;\overline{n} \; β  nm  \displaystyle \to_\beta\; \overline{n}\,\overline{m} \; β  mn\displaystyle \twoheadrightarrow_\beta\; \overline{m^{\,n}}

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

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

pred\displaystyle \mathbf{pred} =λn.fst(n  (λp.pair(sndp)(succ(sndp)))  (pair00))\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(sndp)(succ(sndp))= \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 pair00\mathbf{pair}\,\overline{0}\,\overline{0}:

pred  n  \displaystyle \mathbf{pred}\;\overline{n} \; β  fst(nσ(pair00))  \displaystyle \to_\beta\; \mathbf{fst}\,\bigl(\overline{n}\,\sigma\,(\mathbf{pair}\,\overline{0}\,\overline{0})\bigr) \; β  fst(σn(pair00))\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:

pair00  \displaystyle \mathbf{pair}\,\overline{0}\,\overline{0} \;   pair01  \displaystyle \to\; \mathbf{pair}\,\overline{0}\,\overline{1} \;   pair12  \displaystyle \to\; \mathbf{pair}\,\overline{1}\,\overline{2} \;     \displaystyle \to\; \cdots \;   pairn1n\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(pairn1n)  \displaystyle \twoheadrightarrow_\beta\; \mathbf{fst}\,(\mathbf{pair}\,\overline{n-1}\,\overline{n}) \; β  n1\displaystyle \twoheadrightarrow_\beta\; \overline{n-1}

For 0\overline{0} the chain does not move, so pred0\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] \;   λcn.cx1(cx2((cxnn)))\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…