Programming With Only Functions
Python's lambda
keyword does not get the love it deserves. It can only execute expressions, instead of statements. With this restriction, we can still make something interesting.
A Tiny Computation System
The lambda calculus is a system of computation based on mathematical functions. It is the foundation for functional languages. This contrasts to the Turing machine, which is the foundation for imperative languages. It turns out that, in Python, all we need to implement the lambda calculus is the lambda
keyword.
The identity functions would be:
identity = lambda x: x
Encoding
identity
isn't very interesting, so let's do some calculations. Since we have only functions, we need some way to encode numbers as functions. Let's start with zero
.
zero = lambda f: identity
zero
takes a function f
, and returns the identity function. For clarity, this expands to:
zero = lambda f: lambda x: x
After zero
, we have one
:
one = lambda f: lambda x: f(x)
And then, two
, three
, and four
:
two = lambda f: lambda x: f(f(x))
three = lambda f: lambda x: f(f(f(x)))
four = lambda f: lambda x: f(f(f(f(x))))
Do you see the pattern yet? For a given number n, we can encode it as
n = lambda f: lambda x: f(f(f(....f(x)...)))
Such that there are n f
s. This is method of encoding numbers is known as Church encoding.
Decoding
In order to turn an encoded number into a Python number, we need to decode it using a function of the form:
decode = lambda num: num(f)(x)
We know that decode(zero)
should be 0
and that zero
ignores f
. Therefore x
should be 0
. We also know that f(0)
should be 1
and f(f(0))
should be 2
, and so on. Therefore f
should be lambda x: x + 1
. Substituting this, we get our definition of decode
:
decode = lambda n: n(lambda x: x + 1)(0)
We can check this real quick:
assert decode(zero) == 0
assert decode(one) == 1
Successors
It would be rather tedious if we had to type out every definition of every number we needed. Is there a way we can get the successor of a number from just the definition of the number? Something like the following:
one = succ(zero)
We know a number definition requires two lambda
s, so we'll need three in order to accept the original number. Therefore our definition will look like this:
succ = lambda n: lambda f: lambda x: ????
We know from the definition of decode
that we can get the value of a number n
by calling it it like n(f)(x)
. In order to get the successor of n
, we'll need one more f
. Therefore we have:
succ = lambda n: lambda f: lambda x: f(n(f)(x))
For easy testing, let's declare the following helper function:
equals = lambda x, y: decode(x) == decode(y)
assert equals(succ(zero), one)
Now we can easily define more numbers:
two = succ(one)
three = succ(two)
four = succ(three)
five = succ(four)
six = succ(five)
Addition
Now let us define a function plus
that implements addition. An example usage:
assert equals(plus(two)(three), five)
If we think about it, succ
could have been defined as plus(one)
. Is there a way we can include a one
in the definition fo succ
?
succ = lambda n: lambda f: lambda x: one(f)(n(f)(x))
For plus
, we'd like to be able to specify numbers besides one
, so we'll add another lambda
:
plus = lambda m: lambda n: lambda f: lambda x: m(f)(n(f)(x))
Multiplication
When defining plus
, we saw that if we have a a number m
, m(f)(x)
will add the decoded m
to x
. We also know that one(f)(x)
adds 1
to x
. Therefore, m(one(f))(x)
should be equivalent to m(f)(x)
. We can use this to define multiplication:
mult = lambda m: lambda n: lambda f: lambda x: m(n(f))(x)
assert equals(mult(two)(three), six)
Exponents
We've started to combine these base functions we have in interesting ways. Since we know that m(n(f))
in the defintion of mult
repeats the addition of n
a total of m
times, and that plus(n)
is the addition of n
, we can rewrite mult
to use fewer lambda
s:
mult = lambda m: lambda n: m(plus(n))(zero)
Since using multiplication to define exponents is a similar to using addition to define multiplication, we should be able to adapt this definition of mult
for exp
:
exp = lambda m: lambda n: n(mult(m))(one)
equals(exp(two)(two), four)
More Encodings
We aren't limited to just natural numbers when using the lambda calculus. We can encode boolean logic and lists, allowing us to write programs with control flow and data structures.