Data structures in the lambda calculus
Reading: TAPL Section 5.2, pages 58-63.
Booleans
We represent true and false as functions of two arguments that return their first and second argument, respectively.
tru ≡ λt. λf. t
fls ≡ λt. λf. f
test ≡ λl. λm. λn. l m n
Note that under call-by-value evaluation, test
is not the same as if
in most programming languages. This is the subject of Exercise 5.2.9. (Similarly, and
as defined here is not the same as in most programming languages.)
Exercise. In C (and Java and other languages) you can define Booleans as an enum
:
enum bool {false, true};
Another common example of an enum
is colors:
enum color {red, green, blue};
How would you encode the colors {red, green, blue} in the lambda calculus? Write definitions for red
, green
, blue
, and a function casecolor
that satisfy the following:
casecolor red r g b = r
casecolor green r g b = g
casecolor blue r g b = b
(Here and below, =
means βᵥ-equivalence.)
Pairs
We represent a pair as a function that takes another function b
and calls it on the two members of the pair.
pair ≡ λf. λs. λb. b f s
fst ≡ λp. p tru
snd ≡ λp. p fls
If you’ve done any event-driven programming (like in JavaScript), you may recognize b
as a callback.
Exercise. How would you generalize pairs to triples? Define a function triple
that creates a triple, and functions fst3
, snd3
, and trd3
that extract the first, second, and third members of a triple.
The techniques illustrated above can be combined and generalized to any sort of data structure that has one or more alternatives, each of which contains zero or more values.
Exercise. An option is a simple data structure that either holds a value (some v
) or nothing (none
). Define functions some
, none
, and caseoption
such that:
caseoption (some x) s n = s x
caseoption none s n = n
Natural numbers
We represent the number n as a function taking two arguments, s
and z
, and applying s
to z
n times. These are known as Church numerals.
scc ≡ λn. λs. λz. s (n s z)
zro ≡ λs. λz. z
c0 ≡ zro
c1 = scc zro
c2 = scc (scc zro)
⋮
Something bad happens when we try this out:
scc c0 ≡ (λn. λs. λz. s (n s z)) (λs. λz. z)
⟶ λs. λz. s ((λs. λz. z) s z)
Under call-by-value, we cannot reduce anymore, but this is not the same as c1
. However, they are observationally equivalent (scc c0 = c1
), which we can show as follows:
⟶v λs. λz. s ((λz. z) z)
⟶v λs. λz. s z
≡ c1
You could also think of Church numerals as a kind of data structure: a Church numeral is either zro
, which contains no values, or scc n
, which contains the value n
. In fact, they’re very similar to the options from the exercise above. The difference is that applying an option deconstructs just the option, not its contents, whereas applying a Church numeral deconstructs the whole thing.
This makes it possible to define addition, multiplication, etc. easily. But it also makes defining the predecessor function (prd
) surprisingly difficult. For a while, Church thought it might not be possible, until Kleene thought of a solution (essentially the one in TAPL) while he was having his wisdom teeth out.