Natural Numbers and Arithmetic


The Concept of Numbers in Lambda Calculus

Numbers are fundamental in computation, but in lambda calculus, we don't have built-in numbers like 1, 2, or 3. Instead, we must construct them using functions.

This idea comes from Alonzo Church, the inventor of Church numerals — a way to represent natural numbers using functions alone.

At its core, a number n is simply a function that applies another function n times.

For example:


Church numerals

In lambda calculus, natural numbers are represented using Church numerals — a way to encode numbers as functions. The idea is simple:
A number n is a function that applies another function n times.

The general definition is:

cnλf.λx.fn(x)n

So, basically, cn is a function that represents the number n.

Where:


Addition

Adding two Church numerals means applying one number's function on top of another number’s function:

x+yλx.λy.λp.λq.xp(ypq)

Looks complex, right? Let's break it down. Keep eyes on colors!

Example

Let's compute 2+2:

We substitute c2:

c2+c2(λx.λy.λp.λq.xp(ypq))c2c2

First, we apply c2 instead of x:

(λy.λp.λq.c2p(ypq))c2

Expand c2 instead of y:

λp.λq.c2p(c2pq)

Now, substitute c2λf.λx.f(f(x)):

λp.λq.c2p(λf.λx.f(f(x))pq)

Applying p and q in the inner function λf.λx.f(f(x)):

λp.λq.c2p(p(p(q)))

Now, substitute c2λf.λx.f(f(x)):

λp.λq.λf.λx.f(f(x))p(p(p(q)))λp.λq.(λf.λx.f(f(x))p(p(p(q))))

Let p(p(q))M:

λp.λq.(λf.λx.f(f(x))pM)

Now, push p and M into the inner function:

λp.λq.(p(p(M))

Expand Mp(p(q)):

λp.λq.(p(p(p(p(q)))))λp.λq.(p4(q))

Which is exactly c4!

We have computed 2+2=4 correctly using lambda calculus!


Successor and Predecessor Functions

Reminder:

c0=λx.x
c1=λf.λx.f(x)
F=λx.λy.y
[M,N]=λz.zMN (pair)

So, another way to define numbers could be implemented inductively using pairs.

cn[F,cn1]

Basically, cn is a pair of successor operation F and the previous number cn1.

In other words:

2[F,1][F,[F,0]]

Successor Function (Increment)

The successor function adds one to a given number:

succλx.[F,x]

Where:

Thus, succ applies F once more, effectively adding one.

Let's evaluate succc2:

succc2(λx.[F,x])c2[F,c2]3

So, succc2=c3!

It's like incrementing the number by one. 🚀

int x = 2;
x++;
std::cout << x; // 3

Predecessor Function (Decrement)

The predecessor function subtracts one from a given number:

decλx.xF

Where:

Let's evaluate decc3:

decc3(λx.xF)c3c3F[F,c2]F(λz.zFc2)FFFc2c2

So, decc3=c2!

It's like decreasing the number by one. 🚀

int x = 3;
x--;
std::cout << x; // 2

Zero Check Function

To check if a number is zero, we use the function:

isZeroλx.xT

This function takes a Church numeral and applies it to T (true).

Let's evaluate isZeroc2:

isZeroc2(λx.xT)c2c2T[F,1]T(λz.zF1)TTF1F

Since c2 applies functions twice, it doesn't return T but instead produces F (false).

Correct! isZero(c2)=false.


Special Case: Zero Check on c0

isZeroc0(λx.xT)c0c0Tλx.xTT

Since c0 does nothing, it returns T, meaning isZero(c0)=true!

Success! We can now check if a number is zero in lambda calculus. 🚀

Special Case: Decrement Zero

Let's try to decrement c0:

decc0(λx.xF)c0c0Fλx.xFF

Thus, dec(c0)=F. So, unfortunately (or fortunately), we can't decrement zero in lambda calculus! It returns F instead. No sense, right? 🤷‍♂️