Module 11: Small-step reduction

This module is due at 1:15pm on Tuesday, October 18.

Bound variables

Consider the expression let x = e1 in e2. We say that this expression binds the variable x, that is, it introduces a local definition for x. We say that x is bound in e2, that is, any occurrences of x in e2 refer to the x defined by the let. (However, x is not bound in e1—we will think more about this later.)

For example, in

let x = 2+3 in x + if x < 10 then 3 else 7

both occurrences of x after the in refer to the x defined by the let, so this expression evaluates to 8.

Actually, it is not quite true that any occurrence of x in e2 refers to the x defined by the let. We have to take shadowing into account. For example, consider this expression:

let x = 5 in let x = 6 in x + x

Free variables and substitution

A variable is free if it is not bound. For example, in the expression

let x = 2 in x + y

The x is bound, but y is free.

Small-step reduction

When we have written interpreters before, they have used what is called a big-step semantics: for example, they take an Arith and simply reduce it to a value in one “big step”. There is no way to extract any sort of intermediate expressions or states from the interpreter: running the interpreter is an all-or-nothing sort of operation.

We will now consider an alternative model, where we define what it means for an expression to take a (small) reduction step, resulting in another expression. A reduction step changes as little as possible while still making nontrivial progress. We can then write an interpreter by repeatedly applying reduction steps until the expression cannot take any more steps. For example, consider the expression if 3 + 5 < 7 then 2*5 else (let x = 3 in x+x). The interpArith function would simply take this expression and return 6 (by making recursive calls to interpArith to evaluate the subexpressions, and so on). On the other hand, if we evaluate the expression by repeatedly applying small reduction steps, it would reduce as follows:

if 3 + 5 < 7 then 2*5 else (let x = 3 in x+x)
if 8 < 7 then 2*5 else (let x = 3 in x+x)
if False then 2*5 else (let x = 3 in x+x)
let x = 3 in x+x
3+3
6

Writing an interpreter in this style is usually inefficient compared to a direct (big-step) implementation, but it can be a very useful way to think and reason about the semantics of a language. It is often useful to define both small-step and big-step semantics for a language, along with a formal proof that the two give equivalent results.

Feedback