Lecture 6 — 2015-09-21
Stack machines; the lambda calculus
This lecture is written in literate Haskell; you can download the raw source.
Stack machines
Command | Input stack | Output stack | |
---|---|---|---|
IPush n | s | –> | n:s |
IPlus | n1:n2:s | –> | n1 + n2:s |
ITimes | n1:n2:s | –> | n1 * n2:s |
INeg | n:s | –> | (0-n):s |
The lambda calculus
The lambda calculus is a notation for anonymous functions. We typically write math functions with names, like: f(x) = x + 1. In the lambda calculus, we write functions anonymously. For example, f is equivalent to λx. x + 1.
We introduced an equational semantics with two axioms.
The first, alpha equivalence, says that we can rename variables so long as we do it consistently:
(λx. e) =α (λy. e[y/x])
The second, beta equivalence, says how to apply functions:
(λx. e1) e2 = e1[e2/x]
A variable is free when it’s not under λ that binds it. In λx. x y, the variable y is free but x is bound.
We defined substitution as follows:
x[e/x] | = | e |
y[e/x] | = | y |
(e1 e2)[e/x] | = | e1[e/x] e2[e/x] |
(λx. e’)[e/x] | = | λx. e’ |
(λy. e’)[e/x] | = | λy. e’[e/x] |
Note that the definition above only really makes sense when e—the term being substituted for x—doesn’t have free variables. When e has free variables, we need to worry about capture, where a free variable in e becomes bound. The standard way of defining capture-avoiding substitution is to imagine that we have some infinite source of fresh variables, which don’t appear in any given term we’re considering. We then change the last line to read:
(λy. e’)[e/x] = λz. e’[z/y][e/x] where z is fresh
The freshness of z makes sure that neither e’ nor e mention z at all.
Church encodings
We also defined a bunch of Church encodings, named after Alonzo Church, the progenitor and master encoder of the lambda calculus.
We first defined the Church booleans:
true = λx. &lambda y. x false = λx. &lambda y. x
Then, to check our work, we defined a conditional:
cond = λp. λt. λe. p t e
We can then see that:
cond true e1 e2 = e1
and:
cond false e1 e2 = e2
That is… it really works! The idea here is to operationalize every thing. The booleans are typicaly defined as 2 = { ⊥, ⊤ }, but we instead say that the Church booleans are the functions that make a choice between two things.
We can define numbers, too. A Church numeral is a function that takes two arguments and applies the first one some number of times.
So 0 is the function that never applies its first argument:
zero = λs. λz. z
And 1 applies it once:
one = λs. λz. s z
And two applies it twice:
two = λs. λz. s (s z)
We can write the successor function, which takes a number and then yields its successor.
succ = λn. λs. λz. s (n s z)
Types
Naive Church encodings don’t always work in Haskell due to typing, but we can think of each of these Church encodings as having a type.
type ChurchBoolean = a -> a -> a
type ChurchNumeral = (a -> a) -> a -> a
Shorthand notation
When a function takes a bunch of arguments—like succ above—we sometimes write a single lambda, like so:
succ = λn s z. s (n s z)