Big Steps, Small Steps
Learning Objectives
- You understand big-step call-by-name evaluation semantics for lambda calculus.
- You can derive evaluation trees using the call-by-name rules.
- You understand single-step Ξ²-reduction and weak-head normal forms.
- You can implement call-by-name evaluation and Ξ²-reduction step functions in code.
Introduction
Now that we understand and have implemented capture-avoiding substitution, we can define and implement the semantics of lambda calculus. As there are only three kinds of terms, the semantics are really simple on the surface. In fact, most of the complexity lies in the capture-avoiding substitution.
In Variables and Abstraction we briefly mentioned evaluation in lambda calculus. We begin by defining big-step operational semantics and then move to defining small-step semantics for lambda calculus.
From now on, we use the convention that lambda terms are equal, if they are equal up to -equivalence. This means that when we are working with the term it is the same as β we say they are mathematically equal.
Big-Step Semantics for Lambda Calculus
Unlike the languages studied in the previous chapter, lambda terms donβt evaluate to an integer or a Value.
Instead, lambda terms evaluate to lambda terms, and being a value, almost self-referentially, means being the outcome of evaluation.
To actually make any sense of this idea, letβs look at some examples next. We denote big-step evaluation by the usual symbol. The statements have a βRust analogβ using closures next to them.
Now, letβs define the semantics of lambda calculus. As there are a lot of different ways to define semantics for the lambda calculus, called reduction strategies1 we opt for one of the simplest: call-by-name or CBN.
As we saw in the example, variables donβt evaluate, or rather they evaluate to themselves β variables only play a role in substitution. In fact, if the term is βwell-typedβ we should never need to evaluate a free variable. This is because values are always closed when evaluating well-typed terms.
What it means for a term to be well-typed is defined in the next chapter on the simply typed lambda calculus. For now you can think of well-typed terms primarily as closed terms.
As abstractions house an inner term, we could have a rule to evaluate inside the abstraction. However, this is unnecessary, and done in normalization strategies (which we study in the next chapter) as opposed to the call-by-name strategy.
We will have the following two base rules for the call-by-name evaluation.
They state that any variable or abstraction evaluates to itself.
Another evaluation rule in CBN is one that involves both an abstraction and an application. This is where the βmagicβ happens. If evaluates to an abstraction, we have an abstraction applied to a term β so we can perform a substitution!
Take a moment to read what the rule actually says. Are all the assumptions necessary? Itβs important that the substitution is capture-avoiding β why?
Finally, we add an evaluation rule to use the result of evaluating in the previous rule. This rule can only be used if doesnβt evaluate to an abstraction (or rather evaluates to a nonabstraction), which we denote with .
As an example, letβs write down the evaluation tree of an identity function applied to the identity function
As is -equivalent with , it doesnβt matter what the actual bound variables are. Of course a computational implementation has to decide which bound variables to use (or which fresh variables to use in capture-avoiding substitution), but thatβs just an implementation detail which has no effect on the theory.
Here are some more examples of call-by-name evaluation:
Notice that in the examples, evaluating terms that contain free variables may result in a term which is a variable or even an application. The set of terms that result from CBN evaluation are said to be in weak-head normal form.
Implementing Call-By-Name Evaluation
As the call-by-name evaluation is just a relation, we would like to turn it into at least a partial function.
The function, called cbn evaluates a term by computing the value of ? that evaluates to:
This makes us ask a couple of important questions about the relation:
- Does
?always exist? If so, is left-total. - Is the value of
?unique for a given ? If so, is functional. - Can we write an algorithm to compute
??
We put questions 1 and 2 aside for now and instead focus on question 3 by implementing an algorithm that does call-by-name evaluation.
Single-Step Semantics
Now that we have a basic understanding of evaluation of lambda terms, we expand it with the notion of small-step semantics by studying the single-step -reduction. Instead of evaluating the whole term at once, -reduction happens step-by-step. -reduction also tends to be nonfunctional meaning that a term can take different reduction steps, e.g. reducing the function or the argument position in an application β or any reducible subterm. We denote (single-step) -reduction using an arrow symbol .
-reduction has many slightly different formulations1 and has a lot of literature written about it. We highly recommend reading chapters 2.4 and 2.5 from Selinger: Lecture Notes on the Lambda Calculus.
Weak -Reduction
The notion of -reduction is much more general than defined here. We will learn another, stronger version of it in the next chapter.
Our first version will be like the call-by-name evaluation studied earlier as it doesnβt evaluate the arguments at all. It gets the name weak, because it doesnβt reduce under abstractions and produces weak-head normal forms when reducing until no reductions can be made.
The first, and most important, rule called AppAbs is the βheart of the lambda calculusβ.
Letβs go over it thoroughly.
The rule states that an application term where the left-hand-side is an abstraction reduces to the substitution of by in which is the term .
Here it is written as an inference base rule without premises:
Notice that the AppAbs rule allows us to only perform one substitution at a time, so e.g.
but is not a -reduction step directly.
Next, we explore the other single-step -reduction rules. These rules are like βadaptersβ that let us reduce subterms. For -reduction to do anything useful, we need a way to reduce the left subterm of an application:
This is because the only other rule AppAbs requires an abstraction as the left subterm of an application.
Reducing the right subterm does not get us any closer to being able to use AppAbs.
One can think of
AppAbsas the rule that drives progress. Every other rule is there to get us closer to usingAppAbsor to simplify the lambda term to a nicer form.
Now that you hopefully found a working implementation, the following quizzes will probe your understanding a bit further.
A key detail to unlock the correct answers is to think about the relationship between the AppAbs and App1 rules.
For example, is it possible for to be an abstraction in the premise of App1?
Footnotes
Footnotes
-
See for example Sestoft: Demonstrating Lambda Calculus Reduction or Wikipedia: reduction strategy. β© β©2