Normalisation by Evaluation is a technique in programming languages that is complicated through the presence of variables and higher-order functions, features that we have not yet introduced. [1]
But some of the main ideas can be already explained in a very simple situation without variables and function types. This is the purpose of this lecture.
In mathematics, this idea is sometimes known as van der Waerden's trick as explained in Bergman's course on universal algebra. [2]
We go back to our lectures on syntax and semantics Part 1 and Part 2.
One topic of these lectures was in what sense it is justified to say that the meaning of the (purely formal) language
exp ::= 1 | exp + exp | exp * exp
is captured by the (equally formal) equations
On the one hand, it is important to emphasise that the language and the equations can be run on a machine without any human interference or intelligence.
On the other hand, the language and the equations make sense to us because they agree with our understanding of the natural numbers.
How do we convince ourselves that the equations capture exactly the familiar
How do we know that we have enough equations? Can we drop some equations?
The answer will use ideas we learned about when we discussed abstract reduction systems (ARSs).
We defined a normal form in ARS
In this lecture, we will see a semantic way of giving a notion of normal form. And we will also see how to match these two notions up.
As we explained above, it is important to separate syntax from semantics. Expressions have operations 1,+,* and numbers have operations
Making a distinction between * and
Using different symbols for 1,+ would make the lecture more difficult to read
Even using the different symbols * and
In my experience, there is no one good answer. In programming one tends to not overload and in maths one tends to use overloading a lot, but there are no general rules and one needs to decide on an ad hoc basis, taking into account asthetic considerations, not only objective criteria such as correctness, efficiency, etc
Recall that the meaning of our language exp
's is given by the semantics (meaning function)
The new idea is to, conversely, define a function
For example,
Exercise: Show that
We can now define the semantic normal form of an expression
Question: Which equations between exp
's are needed to guarantee that
for all expressions
For maximum effect pause the lecture and try to find out yourself.
We reason by induction on the three rules that make the definition of
exp ::= 1 | exp + exp | exp * exp
If
If
Exercise: Justify all steps. Which equations are needed?
(Bonus question: Which rules of equational logic are used?)
Similarly, if
To conclude, we have proved by induction on the definition of
for all
Exercise: Can you confirm that the argument above only used the following equations?
Theorem 1: The equations
allows us deduce by equational reasoning the equations
for all expressions
exp ::= 1 | exp + exp | exp * exp
Remark: This theorem has both a logical interpretation and a computational interpretation, as we are going to see next.
In this section, we show that Theorem 1 has a logical interpretation. In fact, we can reformulate Theorem 1 as a so-called completeness result.
Theorem 2 below states that the equations
are sound and complete for the language given by
exp ::= 1 | exp + exp | exp * exp
and the interpretation in the natural numbers given by
In symbolic notation we write this as
Theorem 2:
Proof: Soundness means that for all expressions
hold for natural numbers. But these are just properties of numbers we are familiar with.
Completeness is the converse, namely
This follows from
where the "
This finishes the proof.
To summarise, for all expressions
from the equations
One way in which our language
Question: Can the equations
be derived from the equations
or can they not?
So far in this lecture all computation happened on the right-hand side of
On the left-hand side, we only have a formal language and a set of equations that guarantee, according to Theorem 2,
But we already discussed in one of the first lectures the idea of turning equations into computation rules by reading them from left to right. We pick this idea up now again.
Exercise: You showed in a previous exercise that the proof of Theorem 1 only uses the equations
Show that the proof of the theorem uses these equations only from left to right.
Exercise: Show that the previous exercise can be extended to a proof of the following theorem.
Theorem 3 below says that the rewrite rules
define an ARS
In symbolic notation we write this as
Theorem 3:
Activity: Think about the similarity of Theorem 2 and 3.
Remark: The second theorem shows that the semantic normal forms of the first theorem are also syntactic normal forms (ie normal forms of
Questions:
While the example we considered is simple, the method is very general.
A typical situation is as follows.
We start with a semantic domain
First, one develops a syntax that allows to denote all objects of
If we have done our work well,
This means that we do not yet fully capture
To solve this problem, we identify special terms, sometimes called "canonical terms" or "semantic normal forms", that we are going to use as "canonical representatives" of elements of
which preserves the semantics in the sense that
The interesting, and sometimes difficult, step is now to identify a nice set of equations on terms that allows us to prove (using the rules of equational logic)
for all
Since the equations form an equivalence relation on
which, from left to right is
This isomorphism establishes that up to the naming of elements the domain
In particular, all computations that can be done in the structure
A lot of graduate-level information on application of NBE and related topics is available at the webpage of the Summerschol on Metaprogramming. Already the page of abstracts is an interesting read, but there are also very good teaching materials. If you want to go deeper into this the paper, available from Wadler's homepage on how to apply these ideas to implement domains specific languages could be a good starting point. Elsewhere, Dybier presents an introduction and Abel gives an advanced treatment that also contains some historical and other remarks of general interest. ↩︎
I found his discussion starting after the proof on page 37 particularly insightful. ↩︎
The symbol "1" is overloaded as we use it both for a term of type exp
and for the number
The rule of logic used here is (refl). ↩︎
We will learn later that this means that