| < Free Open Study > |
For the rest of the chapter, we consider the syntax and operational semantics of the lambda-calculus in more detail. Most of the structure we need is closely analogous to what we saw in Chapter 3 (to avoid repeating that structure verbatim, we address here just the pure lambda-calculus, unadorned with booleans or
As in Chapter 3, the abstract grammar defining terms (on page 53) should be read as shorthand for an inductively defined set of abstract syntax trees.
Let
V
be a countable set of variable
x T for every x V ;
if t 1 T and x V , then λx.t 1 T ;
if t 1 T and t 2 T , then t 1 t 2 T .
The
The set of
free variables
of a term
t
, written
FV
(
t
), is defined as
|
FV ( x ) |
= |
{ x } |
|
FV ( λx,t 1 ) |
= |
FV ( t 1 ) \ { x } |
|
FV ( t 1 t 2 ) |
= |
FV ( t 1 ) FV ( t 2 ) |
Give a careful proof that FV ( t ) ≤ size ( t ) for every term t .
The operation of substitution turns out to be quite tricky, when examined in detail. In this book, we will actually use two different definitions, each optimized for a different purpose. The first, introduced in this section, is compact and intuitive, and works well for examples and in mathematical definitions and proofs. The second, developed in Chapter 6, is notationally heavier, depending on an alternative "de Bruijn presentation" of terms in which named variables are
It is instructive to
|
[x ↦ s]x |
= |
s |
|
|
[x ↦ s]y |
= |
y |
if x ≠ y |
|
[x ↦ s](λy.t 1 ) |
= |
λy.[x ↦ s]t 1 |
|
|
[x ↦ s](t 1 t 2 ) |
= |
( [x ↦ s]t 1 ) ( [x ↦ s]t 2 ) |
This definition works fine for most examples. For instance, it gives
[x
↦
(λz. z w)](λy.x) = λy.z. z w,
which matches our intuitions about how substitution should behave. However, if we are unlucky with our choice of bound variable names, the definition breaks down. For example:
[x
↦
y](λx.x) = λx.y
This conflicts with the basic intuition about functional abstractions that the names of bound variables do not matter - the identity function is exactly the same whether we write it λx.x or λy.y or λfranz.franz . If these do not behave exactly the same under substitution, then they will not behave the same under reduction either, which seems wrong.
Clearly, the first mistake that we've made in the naive definition of substitution is that we have not distinguished between
free
occurrences of a variable
x
in a term
t
(which should get replaced during substitution) and
bound
ones, which should not. When we reach an abstraction binding the
This is better, but still not quite right. For example, consider what happens when we substitute the term z for the variable x in the term λz.x:
[x
↦
z](λz.x) = λz.z
This time, we have made
This
Now we are almost there: this definition of substitution does the right thing when it does anything at all . The problem now is that our last fix has changed substitution into a partial operation. For example, the new definition does not give any result at all for [x ↦ y z](λy. x y): the bound variable y of the term being substituted into is not equal to x , but it does appear free in (y z) , so none of the clauses of the definition apply.
One common fix for this last problem in the type systems and lambda-calculus literature is to work with terms "up to renaming of bound variables." (Church used the term alpha-conversion for the operation of consistently renaming a bound variable in a term. This terminology is still common - we could just as well say that we are working with terms "up to alpha-conversion.")
Terms that
What this means in practice is that the name of any λ -bound variable can be changed to another name (consistently making the same change in the body of the λ ), at any point where this is convenient. For example, if we want to calculate [x ↦ y z](λy. xy) , we first rewrite (λy. x y) as, say, (λw. x w) . We then calculate [x ↦ y z](λw. x w) , giving (λw. y z w) .
This convention
|
[x ↦ s]x |
= |
s |
|
|
[x ↦ s]y |
= |
y |
if y ≠ x |
|
[x ↦ s](λy.t 1 ) |
= |
λy. [x ↦ s]t 1 |
if y ≠ x and y ∉ FV ( s ) |
|
[x ↦ s](t 1 t 2 ) |
= |
[x ↦ s]t 1 [x ↦ s]t 2 |
The operational semantics of lambda-terms is summarized in Figure 5-3. The set of values here is more interesting than we saw in the case of arithmetic expressions. Since (call-
Figure 5-3:
Untyped Lambda-Calculus (λ)
The evaluation relation appears in the right-hand column of the figure. As in evaluation for arithmetic expressions, there are two sorts of rules: the
computation
rule E-APPABS and the
Notice how the choice of metavariables in these rules helps control the order of evaluation. Since
v
2
ranges only over values, the left-hand side of rule E-APPABS can match any application whose right-hand side is a value. Similarly, rule E-APP1 applies to any application whose left-hand side is
not
a value, since
t
1
can match any term whatsoever, but the
Adapt these rules to describe the other three strategies for evaluation - full beta-reduction, normal-order, and lazy evaluation.
Note that, in the pure lambda-calculus, lambda-abstractions are the only possible values, so if we reach a state where E-APP1 has succeeded in reducing t 1 to a value, then this value must be a lambda-abstraction. This observation fails, of course, when we add other constructs such as primitive booleans to the language, since these introduce forms of values other than abstractions.
Exercise 3.5.16 gave an alternative presentation of the operational semantics of booleans and arithmetic expressions in which stuck terms are defined to evaluate to a special constant wrong . Extend this semantics to λ NB .
Exercise 4.2.2 introduced a "big-step" style of evaluation for arithmetic expressions, where the basic evaluation relation is "term
t
| < Free Open Study > |