With basic terms and order of evaluation out of the way, we can finally get to practical things. Like numbers.
Numbers in Lambda Calculus (LC) are usually represented by Church Numerals. These are usually two-argument functions taking a function and a value. Numbers apply the function to the value N times and return the result. Here are some numbers:
0 = λfx.x 1 = λfx.f x 2 = λfx.f (f x) 5 = λfx.f (f (f (f (f x)))) ...
There’s also an encoding of numbers with pairs. But that’s too much overhead for questionable benefit. So let’s stick with regular Church numerals and see that...
This wasn’t immediately clear: numbers are compositions. A Church numeral is applying a function it’s given a certain number of times. So you can compose a function with itself by passing it to a number. Elegant, yet non-obvious.
Imagine you want a function taking the fifth (zero-indexed) element of the linked list. So you strip off list beginnings five times and then get the fifth element directly. Implying that CD(tail) returns the tail of the list and CD(head) returns the head:
head ((5 tail) list) head (((λfx.(f (f (f (f (f x)))))) tail) list) head ((λx.(tail (tail (tail (tail (tail x)))))) list) head (tail (tail (tail (tail (tail list)))))
So numbers are clearly compositions. If we want to increase some number, we can simply take this number and compose another function call on top of it. That’s the logic of Successor function:
succ = λnfx.f (n f x) succ 5 succ (λdy.(d (d (d (d (d y)))))) // α-substitution fx -> dy λfx.f ((λdy.(d (d (d (d (d y)))))) f x) λfx.f (f (f (f (f (f x))))) // η-reduction
These closing parentheses are getting unruly.
I have to introduce another notational element to handle them.
I’ll use
Hopefully that’s not too off-putting.
Peano arithmetics—as far as I was able to understand it—
is all about building a complete set of numeric operations out of
constants and successor operator.
It’s also possible to implement these arithmetics in the toolkit Lambda Calculus provides.
Building on the foundations:
So here’s how addition formula based on
Peano axioms
looks in Lambda Calculus:
While these look quite different, the idea is the same:
apply the successor
Now what Peano arithmetics don’t cover is subtraction.
There are no negative numbers, so there’s no “subtraction is addition of negative number”.
So we need to somehow strip off some function applications from the number.
The smart piece of code that Wikipedia lists is:
Here’s some reduction to show how it works.
Beware: it’s quite overwhelming!
Essentially (you probably got it just from looking at it, right?)
what it does is introduce a proxy function that gets passed to the number.
The first application of this function is returning
Now that the predecessor is covered, subtraction is a piece of cake.
The principle is the same as addition: just apply predecessor N times:
Quite elegant...
if we don’t consider the underlying horrors of
Multiplication and exponentiation are beautiful in LC.
But they are not necessarily apparent in how they work.
So I’ll try to make some reductions and explain how these make their magic.
Here’s multiplication:
The idea is clear: take
To deepen the feeling of displacement, here’s the exponentiation function:
This is basically this application of one number to another one.
Quite intuitive on the surface.
But it gives me a stack overflow when I try to mentally reduce it.
So here are written reduction to understand
So what happens here is indeed this curried number thing.
Now what happens here, especially on the line marked with “NB,” is a crime.
Crime against static type systems and fundamentalist minds.
The value that’s supposed to be a base/zero/init value...
Is used as a function.
This makes Lambda Calculus both more cryptic and beautiful:
This is a takeaway that the exponentiation reduction refreshed in my mind:
(After all, it’s been almost 4 months since the last Making Sense of Lambda Calculus post.)
Don’t be afraid to break the supposed contract your lambdas are establishing.
They are just that: lambdas.
Nothing else.
But there are cases when even the smart currying,
re-interpretation of data as functions and vice versa,
and other arcane stuff doesn’t help.
Like division:
Division is always a problem.
It’s not following many rules that addition and multiplication establish.
And there’s a whole page on
division algorithms
on Wikipedia.
The simplest algo is the subtraction-based one.
Basically subtracting the denominator from the numerator until it’s below the denominator.
Here’s how
stdlambda
implementation of division works:
A lot of things there:
Putting it all together:
And yes, that’s the simplest implementation I can think of.
No elegant stuff like applying numbers to each other.
Justine Tunney lists a division function in her “Lambda Calculus in 383 Bytes”,
but I was not able to decipher how it works.
I’m stuck with advanced things like Y combinator for the seemingly simple operation: division.
Summarizing all of the above:
One type of numeric operations that I haven’t mentioned is comparisons.
Because comparisons get us into the uncharted lands of booleans.
Reviewing booleans is a big topic beyond this (already huge) post.
So booleans come next, in
Making Sense of Lambda Calculus 3!
λfx.f (f (f (f (f (f x]
Addition and Peano Arithmetics
Constants are Church numerals like
// Peano
a + 0 = a
a + S(b) = S(a + b)
// Lambda Calculus:
add = λmn.n succ m,
Deadly Predecessor & Subtraction
pred = λnfx.n (λgh.h (g f)) (λu.x) (λu.u)
pred 3
(λnfx.n (λgh.h(g f)) (λu.x) (λu.u)) 3
(λnfx.n (λgh.h(g f)) (λu.x) (λu.u)) (λfx.f(f(fx]
λfx.(λgh.h(g f)) ((λgh.h(g f)) ((λgh.h(g f)) (λu.x))) (λu.u)
λfx.(λgh.h(g f)) ((λgh.h(g f)) (λh.h x)) (λu.u)
λfx.(λgh.h(g f)) ((λgh.h(g f)) (λh.h x)) (λu.u)
λfx.(λgh.h(g f)) ((λgh.h(g f)) (λd.d x)) (λu.u)
λfx.(λgh.h(g f)) (λh.h((λd.d x) f)) (λu.u)
λfx.(λgh.h(g f)) (λh.h(f x)) (λu.u)
λfx.(λgh.h(g f)) (λv.v(f x)) (λu.u)
λfx.(λh.h((λv.v(f x)) f)) (λu.u)
λfx.(λh.hf(f x)) (λu.u)
λfx.((λu.u) f(f x))
λfx.f(f x)
sub = λmn.n pred m,
Multiplication and Exponentiation
mult = λmnf.m (n f)
Where is
pow = λbe.e b
mult 2 3
(λmnf.m (n f)) 2 3
(λmnf.m (n f)) (λfx.f (f x)) 3
(λmnf.m (n f)) (λfx.f (f x)) (λfx.f (f (f x]
(λnf.(λfx.f (f x)) (n f)) (λfx.f (f (f x]
λf.(λfx.f (f x)) ((λfx.f (f (f x))) f)
λf.(λgy.g (g y)) ((λhz.h (h (h z))) f) // α-substitution
λf.(λgy.g (g y)) (λz.f (f (f z)))
λfy.(λz.f (f (f z))) ((λz.f (f (f z))) y) // NB
λfy.(λz.f (f (f z))) (f (f (f y]
λfy.f (f (f (f (f (f y]
λfx.f (f (f (f (f (f x] // SIX!!!!!
pow 3 2
(λbe.e b) 3 2
(λbe.e b) (λfx.f (f (f x))) 2
(λbe.e b) (λfx.f (f (f x))) (λfx.f (f x))
(λe.e (λfx.f (f (f x)))) (λfx.f (f x))
(λf.λx.f (f x)) (λfx.f (f (f x)))
λx.(λfx.f (f (f x))) ((λfx.f (f (f x))) x)
λx.(λfx.f (f (f x))) ((λfy.f (f (f y))) x) // α-substitution
λx.(λfx.f (f (f x))) (λy.x (x (x y))) // NB
λx.(λfz.f (f (f z))) (λy.x (x (x y))) // α-substitution
λxz.(λy.x (x (x y))) ((λy.x (x (x y))) ((λy.x (x (x y))) z))
λxz.(λy.x (x (x y))) ((λy.x (x (x y))) (x (x (x z)))
λxz.(λy.x (x (x y))) (x (x (x (x (x (x z]
λxz.x (x (x (x (x (x (x (x (x z]
λfz.f (f (f (f (f (f (f (f (f z] // α-substitution
λfx.f (f (f (f (f (f (f (f (f x] // α-substitution
λfx.f (f (f (f (f (f (f (f (f x] // NINE!!!
Functions are all there is to LC.
Is There Division?
div = λnd.Y (λr.λq.(lt q d) 0 (succ (r (- q d)))) n
Y
Apply the function to numerator (
Up Next: Truth or Dare With Booleans
Numbers are compositions.