SICP Exercise 2.6
Question
In case representing pairs as procedures wasn’t mind-boggling enough, consider that, in a language that can manipulate procedures, we can get by without numbers (at least insofar as non-negative integers are concerned) by implementing 0 and the operation of adding 1 as
(define zero (λ (f) (λ (x) x)))
(define (add-1 n)
(λ (f) (λ (x) (f ((n f) x)))))
This representation is known as Church numerals, after its inventor, Alonzo Church, the logician who invented the $λ$-calculus.
Define one
and two
directly (not in terms of zero
and
add-1
). (Hint: Use substitution to evaluate (add-1 zero)
).
Give a direct definition of the addition procedure +
(not in
terms of repeated application of add-1
).
Answer
Definition of One
So, let us do as the question says and use (add-1 zero)
to
arrive at a definition of one
.
(add-1 zero)
We can use the substitution model to evaluate this. Let’s start
with the definition of add-1
.
(λ (f) (λ (x) (f ((zero f) x))))
Then plug in zero
:
(λ (f) (λ (x) (f (((λ (f) (λ (x) x)) f) x))))
This is the fully expanded code for (add-1 zero)
, i.e. for
one
. It looks very complex, but let’s focus on just one part of
this and try to simplify it. In particular, let’s look at the
second half of the code, which looks like this:
(((λ (f) (λ (x) x)) f) x)
The first part of this, (λ (f) (λ (x) x))
, evaluates
to (λ (x) x)
. So we can substitute:
(((λ (x) x) f) x)
We can simplify this further. Because ((λ (x) x) f)
simply
returns f
, the fully simplified piece of code looks like:
(f x)
That is much better than what we started with. Let us now plug this into our original equation and we receive the following:
(λ (f) (λ (x) (f x)))
At this point, we see where the difference is between zero
and
one
. Let’s look at them side by side:
(λ (f) (λ (x) x)) ; zero
(λ (f) (λ (x) (f x))) ; one
One
simply has one more function call in it.
Definition of Two
We can already guess what two
might look like now, but let’s
try deriving it using add-1
, similar to the way we just did it
for one
:
(add-1 one) ; evaluate add-1
(λ (f) (λ (x) (f ((one f) x)))) ; evaluate one
(λ (f) (λ (x) (f (((λ (f) (λ (x) (f x))) f) x))))
; (((λ (f) (λ (x) (f x))) f) x) --> ((λ (x) (f x)) x)
(λ (f) (λ (x) (f ((λ (x) (f x)) x))))
; ((λ (x) (f x)) x) --> (f x)
(λ (f) (λ (x) (f (f x))))
So, just to confirm our previous intuition, let’s look at the three numbers we have now side by side:
(λ (f) (λ (x) x)) ; zero
(λ (f) (λ (x) (f x))) ; one
(λ (f) (λ (x) (f (f x)))) ; two
As suspected, it’s just one more function call.
Definition of +
This should probably be similar to the add-1
-procedure, except
instead of adding one
to n
, we add any number m
.
We can deduce from the add-1
-procedure that, if n
describes
the number to add one
to using (n f)
, maybe we can do
something similar for the f
before that, like so:
(define (church-plus n m)
(λ (f) (λ (x) ((n f) ((m f) x)))))
Let’s test if this works by adding one
and two
.
Warning: the following code is admittedly close to incomprehensible. I
tried my best to explain my thinking with the comments, but even so,
there’s just a lot of λ
s next to each other, which makes it quite
hard to read.
(church-plus one two)
; expand church-plus definition
(λ (f) (λ (x) ((one f) ((two f) x))))
; expand one and two
(λ (f)
(λ (x)
(((λ (f) (λ (x) (f x))) f)
(((λ (f) (λ (x) (f (f x)))) f) x))))
; start simplifying
(λ (f)
(λ (x)
(((λ (f) (λ (x) (f x))) f) (((λ (f) (λ (x) (f (f x)))) f) x))))
; (((λ (f) (λ (x) (f x))) f) (((λ (f) (λ (x) (f (f x)))) f) x)) -->
; ((λ (x) (f x)) (((λ (f) (λ (x) (f (f x)))) f) x))
(λ (f)
(λ (x)
((λ (x) (f x)) (((λ (f) (λ (x) (f (f x)))) f) x))))
; ((λ (x) (f x)) (((λ (f) (λ (x) (f (f x)))) f) x)) -->
; (f (((λ (f) (λ (x) (f (f x)))) f) x))
(λ (f)
(λ (x)
(f (((λ (f) (λ (x) (f (f x)))) f) x))))
; ((λ (f) (λ (x) (f (f x)))) f) -->
; (λ (x) (f (f x)))
(λ (f)
(λ (x)
(f ((λ (x) (f (f x))) x))))
; ((λ (x) (f (f x))) x) -->
; (f (f x))
(λ (f) (λ (x) (f (f (f x)))))
This is exactly the result we would expect, so it seems that this method of addition is valid.