Lecture 19 (2018-03-27)
Lambda calculus: evaluation and step functions, nontermination
The equational theory is the "true" account of what the lambda
calculus means, but it's not a great account of how we *interpret* the
lambda calculus as a programming language. There are several ways to
do so.
### Evaluation function

One way we can understand the lambda calculus is by writing a
mathematical function to interpret it, like we did for arithmetic
expressions. Here's a first attempt:
```
eval :: LC -> LC
eval(x) = ??? -- what do we do here?
eval(λx. e) = λx.e -- evaluate e?
eval(e1 e2) = eval(e'[e2/x]) -- evaluate e2?
where eval(e1) = λx. e'
```
Let's deal with each of the questions in turn. Do we need a case in
`eval` for variables? If we're trying to understand our `eval`
function as running a program, then we only ever want to run *closed*
expressions---and the substitution in the application case makes sure
we close up all of our terms.
Next, should we evaluate inside of a lambda? While the equational
theory leaves us free to work on any part of the term, programming
languages generally avoid evaluating inside of functions before
they've been called. Consider the following Java method:
```Java
public void doStuff() {
throw new RuntimeException("but i'm tired");
}
```
If my code never calls `doStuff`, then I should never see the
exception. That is, we don't evaluate inside of a function until its
called. (There *are* evaluation schemes that do look inside functions;
such reductions are called "full β", and they're not relevant for us.)
We've settled the first two questions with "we'll never have to worry
about it" and "don't jump the gun on evaluating functions". What
should we do with the argument to functions? Should we evaluate the
argument before substituting it into the body or not?
Here, we simply have a choice of *calling convention*. In
*call-by-value* languages, we only call functions with fully evaluated
arguments; in *call-by-name* languages, we don't both evaluating
arguments, figuring we'll get to them when we need to. So there are
two definitions of `eval`:
```
evalCBV :: LC -> LC
evalCBV(λx. e) = λx. e
evalCBV(e1 e2) = evalCBV(e'[e2'/x])
where evalCBV(e1) = λx. e'
evalCBV(e2) = e2'
evalCBN :: LC -> LC
evalCBN(λx. e) = λx. e
evalCBN(e1 e2) = evalCBN(e'[e2/x])
where evalCBN(e1) = λx. e'
```
CBV and CBN differ in terms of termination: sometimes CBN terminates
when CBN doesn't. Our example was the following term e:
```
ω = λx. x x
Ω = ω ω
true = λa b. a
one = λs z. s z
e = true one Ω
```
In particular, `evalCBN(e) = one` but `evalCBV(e)` was undefined.
### Step relations

We can also interpret the lambda calculus using a step relation. To do
so, we'll need to identify what it means to be "fully evaluated". We
say that the *values* of the lambda calculus are lambdas---they're the
objects in the language that don't have more evaluation to do (until
they're called). We write `v` instead of `e` when we mean not just any
term, but a value.
We can define the step relations for both CBN and CBV styles. First,
here are the rules for the CBV lambda calculus, writing `e -->CBV e'`
when `e` steps to `e'` under call-by-value semantics:
```
e1 -->CBV e1'
-------------------
e1 e2 -->CBV e1' e2
e2 -->CBV e2'
-------------------
v1 e2 -->CBV v1 e2'
---------------------------
(λx. e1) v2 -->CBV e1[v2/x]
```
Note that we've defined a left-to-right evaluation order. We could have defined a right-to-left order by making sure `e2 -->CBV* v2` before we stepped `e1`. Recall that `*` takes the reflexive transitive closure of a relation, i.e.:
```
--------
e -->CBV* e
e -->CBV e' e' -->CBV* e''
------------------------------
e -->CBV* e''
```
The CBN relation is similar, but it doesn't bother to evaluate its arguments:
```
e1 -->CBN e1'
-------------------
e1 e2 -->CBN e1' e2
---------------------------
(λx. e1) e2 -->CBN e1[e2/x]
```
#### Nontermination

Let `ω = λx. x x`. Consider `Ω = ω ω`. Observe, using either `-->CBV` or `-->CBN`:
```
Ω = ω ω
= (λx. x x) (λx. x x)
--> (λx. x x) (λx. x x)
--> (λx. x x) (λx. x x)
--> ...
```
We've found a term that runs forever... yikes! What does `evalCBV(Ω)` do?
```
evalCBV(Ω) = evalCBV(ω ω)
= evalCBV((λx. x x) (λx. x x))
= evalCBV((x x)[(λx. x x)/x])
= evalCBV((λx. x x) (λx. x x))
```
Uh oh... `evalCBV` isn't well defined here. (Neither is
`evalCBN`---check for yourself!) In fact, any evaluator for the lambda
calculus *must* be a partial function---the lambda calculus is a
universal computing machine, so it suffers from the undecidability of
the Halting problem. We'd be in seriously contradictory territory if
we were able to write a total function that evaluated every possible
lambda calculus expression!
Note that step functions let us reason clearly about nontermination:
we can say that a term `e` diverges when for all `e'` such that `e
-->* e'`, there exists an `e''` such that `e' --> e''`.
### Evaluation relations

The step functions let us reason clearly about nontermination, but
they don't let us "evaluate" a program in one go. We can define an
evaluation relation that does just that while avoiding the
definitional issues in `evalCBV` and `evalCBN`. We'll say `e ==>CBV v`
when `e` evaluates to `v` under call-by-value semantics.
```
---------------
λx. e ==>CBV λx. e
e1 ==>CBV λx. e e2 ==>CBV v2 e[v2/x] ==> v
------------------------------------------------
e1 e2 ==>CBV v
```
And for CBN:
```
---------------
λx. e ==>CBN λx. e
e1 ==>CBN λx. e e[e2/x] ==> v
--------------------------------
e1 e2 ==>CBN v
```
It's worth running some examples (see [lecture 17](Lec17.html))
through this semantics just to make sure you understand.
Just to understand how our evaluation relations deal with
nontermination... can you build a finite derivation for `Ω ==>CBV v`
for some value `v`?
### Closures

Finally, there's a style of interpreter that gets a little closer to
how languages are actually implemented: real programming languages
don't typically use substitution directly, but instead keep an
*environment* mapping variable names to values.
A brief aside: environments are like the stack, holding values that
are in scope now but might not be later; stores are like the heap,
holding values that persist (and might be mutated!) across function
calls.
The main advantage of using environment is one of efficiency:
substitution is a slow operation, walking over the whole term every
time.
We begin by defining environments and values mutually
recursively. Environments are finite maps from variable names to
values. The only kind of value we'll have are lambdas, just like
above, but we'll need to keep alongside each lambda an environment
with all of the substitutions we *would have* done. Such a pair of a
lambda and an environemnt is called a *closure*: the environment
"closes up" the lambda abstraction.
```
ρ ::= . | ρ[x |-> v]
v ::= <λx. e, ρ>
```
Now we can define our evaluation relation as follows:
```
ρ(x) = v
--------------------------
ρ, x ==>CBV v
--------------------------
ρ, λx. e ==>CBV <λx. e, ρ>
ρ e1 ==>CBV <λx. e, ρ'> ρ, e2 ==>CBV v2 ρ'[x|->v2], e ==> v
-----------------------------------------------------------------
ρ, e1 e2 ==>CBV v
```
Try seeing how `(λa b. a) zero one` evaluates in the empty
environment.
We can go from values back to terms like so:
```
toExpr(v) :: LC
toExpr(<λx. e, ρ>) = close(λx. e, ρ)
close(e, ρ) :: LC
close(x, ρ) = toExpr(ρ(x))
close(e1 e2, ρ) = close(e1, ρ) close(e2, ρ)
close(λx. e, ρ) = λx. close(e, ρ\x)
```
Note that `ρ\x` should be read as "the environment `ρ` but without any
binding for the variable `x`".
### Tying it all together

Each of these definitions should in some sense be
equivalent. Formally, the following statements should either all be
true or all be false:
1. `evalCBV(e) = v`
2. `e -->CBV* v`
3. `e ==>CBV v`
4. `., e ==>CBV v'` and `toExpr(v') = v`
Note that (2) and (3) are really very closely related: their
implementation in code is nearly identical. All three implementation
styles---evaluation function using substitution, step function, or
evaluation function using closures---are good ones for
[HW06](/hw/Hw06.html).