# Encoding Booleans, Tuples, Lists, and Numbers #
The lambda calculus can represent any computable function?
We need to do some work to show how to represent some of the functions
we've become acquainted with.
## Booleans ##
We'll start with the `if ... then
... else...` construction we saw last week:
if M then N else L
For a boolean-valued expression `M`, the displayed expression should evaluate to whatever `N` does
if `M` evaluates to `'true`, and to whatever `L` does if `M` evaluates to `'false`.
In order to encode or simulate such an `if` clause in the Lambda Calculus, we
need to settle on a way to represent `'true` and `'false`.
We could simply add the constants `'true` and `'false` to the
language, since it does make sense to extend the Lambda Calculus by adding constants.
However, that would also require complicating the
interpretation of the language; at the very least, we would need more
than just beta-reduction as our engine of computation. In the spirit
of computational minimalism, let's see how far we can get with the
"pure" lambda calculus, without any special constants.
Let's get rid of the parts of the `if` statement that are
just syntactic window-dressing. That is, let's get rid of the
`if`, the `then`, and the `else`:
M N L
Recall that our convention is that values associate left to right, so
this series of terms would be evaluated as:
((M N) L)
If this expression is supposed to have the meaning of `if M then N
else L`, then we need to find a value for `'true` such that when it is
substituted in place of `M`, the expression evaluates to `N`. The
function we're looking for should take two arguments: first `N`, then `L`,
and it should throw away `L` and return `N`.
We've already seen such a function. We called it **K**: `(\x y. x)`.
Let's test:
((K N) L) == (((\x y. x) N) L) ~~> ((\y. N) L) ~~> N
Sucess! In the same spirit, `'false` could be **K I**, which reduces to `(\y x. x)` (also written as `(\y. (\x x))`):
(((K I) N) L) == ((((\x y. x) (\x x)) N) L)
~~> (((\y. (\x x)) N) L)
~~> ((\x x) L)
~~> L
So have seen our first major encoding in the lambda calculus:
"true" is represented by **K**, and "false" is represented by **K I**.
We'll be building up a lot of representations in the weeks to come,
and they will all maintain the discipline that if a expression is
meant to be interpreted as a truth value (i.e. as a Boolean), it will
be equivalent to (convertible with) **K** or **K I**.
In class, we explained how "and" could be thought of as the function, here written
in Kapulet syntax:
lambda p q. if p then q else false
or:
lambda p q. if p then q else p
Given that we know how to express `if ... then ...` in terms of our encoded Booleans,
we can represent this easily in the Lambda Calculus as either:
\p q. p q (\y x. x)
or:
\p q. p q p
Typically there will be more than one way to encode a given function into the Lambda Calculus, even holding fixed
all your other decisions about how to encode other functions. In this case, we can use either representation
because we're assuming that the `p` only gets bound to arguments that are either `\x y. x` or `\y x. x`. We don't make any
promises about what will happen if our encodings are abused, that is, supplied with arguments they weren't designed to accept.
If `p` is bound to `\x y. x`, then the result of both of the above displayed expressions will be whatever `q` is bound to.
If on the other hand, `p` is bound to `\y x. x`, then the two displayed expressions will return the same result, namely that function.
Despite this, the two displayed expressions are two different lambda terms, and
are not convertible. It's only within the frame of our assumptions about the
restricted arguments we're thinking of supplying them that they behave exactly
alike.
You can try out these expressions in the [[lambda evaluator|code/lambda evaluator]]:
let true = \x y. x in
let false = \y x. x in
let and = \p q. p q p in
and true false
will reduce or "normalize" to `\y x. x`, or false, just as you'd predict. The `let true =` stuff isn't officially part of the Lambda Calculus language. It's just a convenient shorthand that lets you use the lambda evaluator more easily. Behind the scenes, the displayed expression above gets translated to:
(\true.
(\false.
(\and. and true false)))
(\x y. x) ; this gets bound to variable "true"
(\y x. x) ; this gets bound to variable "false"
(\p q. p q p) ; this gets bound to variable "and"
We expect you'll agree that the first is easier to write and to read.
In Scheme (Racket), all of these notions can be defined like this:
(define true (lambda (x) (lambda (y) x)))
(define false (lambda (y) (lambda (x) x)))
(define lambda-and (lambda (p) (lambda (q) ((p q) p))))
and then used like this:
((lambda-and true) false)
which will evaluate to a function, that happens to be the same function `false` is bound to. Most Scheme interpreters like Racket will helpfully display the function with the name, if any, that was first defined to be bound to it. So we'll see the result as:
#
The funny calling pattern, where we write `((lambda-and true) false)` instead of just `(lambda-and true false)`, is because that's how you have to write curried functions in Scheme. Similarly for why we have `(lambda (x) (lambda (y) x))` instead of just `(lambda (x y) x)`.
It's possible to do the next few weeks of assignment without using a Scheme interpreter, however
we do recommend you [get Scheme installed on your
computer](/how_to_get_the_programming_languages_running_on_your_computer), and
[get started learning Scheme](/learning_scheme). It will help you understand the ideas better to experiment with it.
There's also a (slow, bare-bones, but perfectly adequate) version of Scheme available for online use at . (Unfortunately, though, that Scheme implementation will only display the result of `((lambda-and true) false)` as `#`. You won't be able to see if it's the function assigned to `true` or the function assigned to `false`. You'd have to feed that result some more arguments to see how it behaved.)
You should also be experimenting with this site's [[lambda evaluator|code/lambda evaluator]].
## Tuples ##
In class, we also showed you how to encode a tuple in the Lambda Calculator. We did it with an ordered triple, but the strategy generalizes in a straightforward way. (Some authors just use this strategy to define *pairs*, then define triples as pairs whose second member is another pair, and so on. Yech. If you keep a firm grip on your wits, that can also be made to work, but it's extremely likely that people who code in that way are going to lose their grip at some point and get themselves in a corner where they'll regret having made that decision about how to encode triples. And they will be forced to add further complexities at later points, that they're probably not anticipating now. The strategy presented here is as elegant as it first looks, and will help you program more hygienically even when your attention lapses.)
Our proposal was to define the triple `(a, b, c)` as:
\f. f a b c
To extract the first element of this, you'd write:
(\f. f a b c) fst_of_three
where `fst_of_three` is the function `\x y z. x`:
(\f. f a b c) (\x y z. x) ~~>
(\x y z. x) a b c ~~>
(\y z. a) b c ~~>
(\z. a) c ~~>
a
Here are the corresponding definitions in Scheme (Racket):
(define make-triple (lambda (a) (lambda (b) (lambda (c)
(lambda (f) (((f a) b) c))))))
(define fst_of_three (lambda (x) (lambda (y) (lambda (z) x))))
(define snd_of_three (lambda (x) (lambda (y) (lambda (z) y))))
(define trd_of_three (lambda (x) (lambda (y) (lambda (z) z))))
Then:
(define p (((make-triple 10) 20) 30))
(p fst_of_three) ; will evaluate to 10
(p snd_of_three) ; will evaluate to 20
If you're puzzled by having the triple to the left and the function that "uses" or "consumes" or operates on it to the right, think about why it's being done this way: the triple is a package that will be used in coordination with some function for operating on its elements. We don't know in advance what that function will be. And it's not obvious how to make the triple be some structure that the function can "look inside" and extract the elements from. We're still trying to *figure out* how to define such structures! But what we can do is make *the triple take the function as an argument*, and return *the result of* operating on its elements with that function. In other words, the triple is a higher-order function: a function that expects another function as an argument.
(Consider the similarities between this definition of a triple and a generalized quantifier. This is in fact our first taste of "continuations" in the course, which are a systematic pattern for inverting the naive order of who-is-the-argument? and who-is-the-operator?)
If you really want to, you can disguise what's going on like this:
(define lifted-fst_of_three (lambda (p) (p fst_of_three)))
Then you could say:
(lifted-fst_of_three p)
instead of:
(p fst_of_three)
Of course, the last is still what's happening under the hood.
(Remark: `(lifted-f (((make-triple 10) 20) 30))` stands to `((((make-triple 10) 20) 30) f)` as
`((((make-triple 10) 20) 30) f)` stands to `(((f 10) 20) 30)`.)
## Lists ##
There are multiple ways to encode lists, and also multiple ways to encode numbers. We are going to start with what we think are the most natural and elegant encodings. Historically these were the first encodings of numbers but not of lists.
In seminar we discussed two further functions for working with lists or sequences. Reverting to Kapulet syntax, these functions work like this:
fold_right (f, z) [10, 20, 30]
That will evaluate to whatever this does:
f (10, f (20, f (30, z)))
For example, if we let `f` be `(+)` and `z` be `0`, then it will be `10 + (20 + (30 + 0))` or `60`. Another example, if we let `f` be `(&)` and `z` be `[]`, then `fold_right ((&), []) [10, 20, 30]` will be `10 & (20 & (30 & []))` or `[10, 20, 30]`, the same sequence we began with.
The other function works like this:
fold_left (f, z) [10, 20, 30]
That will evaluate to whatever this does:
f (f (f (z, 10), 20), 30)
With a commutative operator like `(+)`, it makes no difference whether you say `fold_right ((+), z) xs` or `fold_left ((+), z) xs`. But with other operators it will make a difference. We can't say `fold_left ((&), []) [10, 20, 30]`, since that would start by trying to evaluate `[] & 10`, which would crash. But we could do this:
let
flipped_cons match lambda (xs, x). x & xs # also expressible as `uncurried_flip (&)`
in fold_left (flipped_cons, []) [10, 20, 30]
and that would evaluate to `flipped_cons (flipped_cons (flipped_cons ([], 10), 20), 30)`, in other words, to `30 & (20 & (10 & []))`, or `[30, 20, 10]`. So this reverses the sequence we began with.
In class we considered how to express other list operations in terms of these. For example, we saw that we could define `length xs` as:
fold_right ((lambda (_, z). 1 + z), 0) xs
And we could define `map g xs` as:
fold_right ((lambda (x, zs). g x & zs), []) xs
Here is [a nice website](http://stevekrouse.github.io/hs.js) we found that lets you evaluate these things step by step. Just click on the subexpressions to evaluate them.
We also saw in seminar how to define `fold_right`. We could do this in Kapulet like this:
letrec
fold_right (f, z) xs = case xs of
[] then zs;
y & ys then f (y, fold_right (f, z) ys)
end
in fold_right
(In some presentations you may see this expecting `f` to be a curried function `lambda x z. ...` rather than the uncurried `lambda (x, z). ...` that I'm expecting here. We will be shifting to the curried form ourselves momentarily.)
We suggested in class that these functions were very powerful, and could be deployed to do most everything you might want to do with a list. Given how our strategy for encoding booleans turned out, this ought to suggest to you the idea that *folding is what we fundamentally do* with lists, just as *conditional branching is what we fundamentally do* with booleans. So we can try to encode lists in terms of lambda expressions that will let us perform folds on them. We could try to do this with either right-folds or left-folds. Either is viable. Some things are more natural if you use right-folds, though, so let's do that.
What should the encoding look like? We don't know *what* function and *what* starting value someone might want to fold over our list!
Wait, does that remind you of anything?
Good. I knew it would.
Indeed, we'll make our encoded lists consist of higher-order *functions* that take the `f` and the starting value `z` to be folded *as arguments*. So the list `[a, b, c]` should look something like this:
\f z. SOMETHING
Now, what should the `SOMETHING` be? Well, when we supply an `f` and a `z` we should get back the right-fold of those over `[a, b, c]`, so the answer should evidently be:
\f z. f a (f b (f c z))
Here we work with curried functions, because that's how the Lambda Calculus does things. You wouldn't want to build up a tuple using the mechanisms described above, and then supply `f` as an argument to that tuple, and so on. That would be a lot of red tape for no benefit. In the Lambda Calculus, it's simpler to just work with curried functions as our natural idiom.
So if `[a, b, c]` should be the displayed higher-order function above, what should `[c]` be? Evidently:
\f z. f c z
Now what should the empty list `[]` be? Think about it...
Did you arrive at an answer?
I hope it was this one: `\f z. z`. Because when we fold a function `f` and a starting value `z` over the empty list, we just get back whatever the starting value `z` was.
We saw before what a `make-triple` function would look like. What about a `make-list` function, or as we've been calling it, "cons" (`&` in Kapulet)? Well, we've already seen what the representation of `[]` and `[c]` are. In that simplest case, the `cons` function should take us from the encoding of `[]`, namely `\f z. z` to the encoding of `[c]`, namely `\f z. f c z`, as a result. Here is how we can define this:
let cons = \c cs. \f z. f c (cs f z)
in ...
Let's walk through this. We supply this function with our `c` and `[]` as arguments. So its `c` gets bound to our `c`, and its `cs` gets bound to our `[]`, namely `\f z z`. Then our result is a higher order function of the form `\f z. f c SOMETHING`. That looks good, what we're after is just this, except the `SOMETHING` should be `z`. If we just simply said `z` here, then our `cons` function would always be giving us back a singleton list of the form `\f z. f x z` for some `x`. That's what we want in this case, but not in the general case. What we'd like is to use the second argument we fed to `cons`, here `[]` or `\f z. z`, and have it reduce to `z`, with the hope that the same strategy wuld make more complex second arguments reduce to other appropriate values. Well, `\f z. z` expects an `f` and a `z` as arguments, and hey we happen to have been given an `f` and a `z`, supplied by the consumer or user of the sequence `[c]` that we're building. So let's just give them to `\f z. z` as its arguments, and it gives us `z`. In other words, we make `SOMETHING` be `cs f z`.
Formally:
cons c []
is:
(\c cs. \f z. f c (cs f z)) c (\f z. z) ~~>
(\cs. \f z. f c (cs f z)) (\f z. z) ~~>
\f z. f c ((\f z. z) f z) ~~>
\f z. f c ((\z. z) z) ~~>
\f z. f c z
which is just the representation of `[c]` we were after.
Will this work when more complex values are supplied as the second argument to `cons`? Let's see:
cons b [c]
is:
(\c cs. \f z. f c (cs f z)) b (\f z. f c z) ~~>
(\cs. \f z. f b (cs f z)) (\f z. f c z) ~~>
\f z. f b ((\f z. f c z) f z) ~~>
\f z. f b ((\z. f c z) z) ~~>
\f z. f b (f c z)
which looks right. Persuade yourself that the `cons` we've defined here, together with the representation `\f z. z` for the empty list, always give us the correct encoding for complex lists, in terms of a function that takes an `f` and a `z` as arguments, and then returns the result of right-folding those values over the list elements in the appropriate order.
You may notice that the encoding we're proposing for `[]` is the same encoding we proposed above for `false`. There's nothing deep to this. If we had adopted other conventions, we could easily have made it instead be `true` and `[]` that had the same encoding.
Now we saw above how to define `map` in terms of `fold_right`. In Kapulet syntax, `map g xs` was:
fold_right ((lambda (x, zs). g x & zs), []) xs
In our Lambda Calculus encoding, `fold_right (f, z) xs` gets translated to `xs f z`. That is, the list itself is the operator, just as we saw triples being. So we just need to know how to represent `lambda (x, zs). g x & zs`, on the one hand, and `[]` on the other, into the Lambda Calculus, and then we can also express `map`. Well, in the Lambda Calculus we're working with curried functions, and there's no infix syntax, so we'll replace the first by `lambda x zs. cons (g x) zs`. But we just defined `cons`, and the lambda is straightforward. And we also just defined `[]`. So we already have all the pieces to do this. Namely:
map (g, z) xs
in Kapulet syntax, turns into this in our lambda evaluator:
let empty = \f z. z in
let cons = \c cs. \f z. f c (cs f z) in
xs (\x zs. cons (g x) zs) empty
Try out this:
let empty = \f z. z in
let cons = \c cs. \f z. f c (cs f z) in
let map = \g xs. xs (\x zs. cons (g x) zs) empty in
let abc = \f z. f a (f b (f c z)) in
map g abc
That will evaluate to:
\f z. f (g a) (f (g b) (f (g c) z))
which looks like what we want, a higher-order function that will take an `f` and a `z` as arguments and then return the right fold of those values over `[g a, g b, g c]`, which is `map g [a, b, c]`.
## Numbers ##
It's noteworthy that when he was developing the lambda calculus, Church had not predicted it would be possible to encode the numbers. It came as a welcome surprise.
*More coming...*