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.
Here and are simply two arguments (mnemonic: the “true” branch and the “false” branch): returns the first, the second. So a boolean value is itself a choice between two — a ready-made .
Here does almost nothing on its own: it takes a boolean and hands it two branches and , and picks the right one. is just as simple: it gives the boolean its branches in reverse order, so truth chooses falsehood and falsehood chooses truth.
In the first boolean decides what to return: if is true, the result depends on ; if false, falsehood is returned at once. In it is the opposite: if is true, truth can be returned immediately; if false, we have to look at .
Let’s substitute truth ( , ) and perform the reductions:
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”:
hands the pair , hands it , and the pair gives back the right component:
Notice how neat this is: and 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 is “apply a function times”:
Important: and 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 and an argument — and it applies exactly as many times as the number specifies:
Now let’s learn to compute — all arithmetic grows from one trick: “apply the required number of times”.
Let’s start with adding one: tacks one more application of onto the number:
Addition continues the idea — it applies first times, then more:
Multiplication is already repeated addition:
Exponentiation is repeated multiplication, and its notation is very short:
In the arguments simply swap places: is applied as a function times, which yields .
Subtraction is surprisingly hard: is built through pairs — we run a pair along the number and take the first component:
By a well-known legend, Stephen Kleene came up with the predecessor function (, predecessor) — the key to subtracting Church numerals — right in the dentist’s chair.
Everything “subtractive” rests on : subtraction itself is repeated , and division is repeated subtraction. We give no separate λ-definition for division: there is no new idea in it — the same in a loop, only a bulkier expression.
Let’s check on a general numeral . Denote the step — it shifts the pair: it drops the first component and grows the second:
Substitute into the definition — that means applying exactly times to the starting pair :
The pairs run along a chain — the second component counts the steps, the first lags by one:
It only remains to take the first component:
For the chain does not move, so — we take the predecessor of zero to be zero.
feeds the number a function that answers at every step, with the start value : zero has no steps — remains; any other number has at least one step, which turns it into .
A list is its own right fold (the fold encoding):
The same trick in Haskell — the type of : a list is fully determined by how it folds. A Church numeral is “a list of 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…