2.2 Normalization by Evaluation
2.2.3 Name Generation Environment
It appears in the evaluation of nf(r) =↓JrK↑, in each recursive call of↓ at arrow type:
↓ρ→σ f =λx. ↓σ f(↑ρ x) x new
The term ↓σ f(↑ρ x) can contain free variables which are either free variables of r
or which have been created by other recursive calls of↓. The side condition ”xnew” means thatx should be different from these variables.
These two situations are exposed in the example below:
Example 2. A newly created variable should be different from one occurring free in
r. Let r be the term y with the typing y:o→o`y:o→o
nf(r) =↓JyK↑ =↓(↑o→oy) =λx.↓o (↑o→oy)(↑o x) x new =λx.↓o (↑o→oy)(x) x new =λx.(↑o→o y)(x) =λx.↑o y↓o x =λx.↑o yx =λx.yx
A newly created variable should be different from already created variables. Let r be the term λy.y with the typing `λy.y : (o →o)→o→o
nf(r) =↓Jλy.yK↑ =λx. ↓o→o (Jλy.yK↑(↑ o→o x)) x new =λx. ↓o→o (↑o→o x) =λx.λy. ↓o ((↑o→o x)↑o y) y new =λx.λy.(↑o→ox)y =λx.λy. ↑o x(↓o y) =λx.λy.xy
Said informally, in the expression
λx.↓σ f(↑ρx) x new
the function f already ”contains” the necessary information (i.e., the variables al- ready used) to compute the new variable x. However f is a function, and to extract this information, one has to apply f to an argument, but we are precisely looking for an appropriate argument of f.
To solve this dilemma, a possible solution is to record along the evaluation of
↓JrK↑ which variables have been used (those free in r and those already created by a call of ↓ at arrow type).
In fact, all we need to create new variables, is to have at hand a set of variables, which does not contain the already used variables. Hence, we do not even need to record all the used variables, but merely a set of unused ones. This weaker solution can read informally as follows:
• We begin the evaluation of ↓ JrK↑ with a denumerable set e = er of variables
not containing those free in r,
• when evaluating ↓ at arrow type, we first pick a new variable x from e, and continue the computation with the set e\ {x}.
We can see in this informal exposition that the set e acts exactly like an envi- ronment. The computation for the function↓at arrow type needs to read a value, a fresh variable x, from e and run some subcomputations in an updated environment
ex (without this fresh variable).
Definition 2.20 (Name Generation Environment). We define a set of name gen- eration environment or set of environment for short, as a set E together with an update function (−)− :E →Var→E and an access function new:E →Var.
The extension of the update function e− : V → E to a function from a list of variables e :L(V)→E is defined in a canonical way by
ex,−→x ::= (ex)−→x
eε::= e.
The function (−)− and new have moreover to satisfy the following property for all e∈E:
∀ −→x , x6=new(ex,−→x) (†)
An environment e ∈ E is meant to be a denumerable set of variables, the update function −x applied to an environment e is meant to remove a variable x from e,
and new(e) to pick a variable from e.
An explanation for the condition (†) is given after having introduced the following notation.
Notation 9. The condition that the new function applied to an environment e∈E
should never return a given variable x can be expressed by:
∀ −→x , x6=new(e−→x)
We will abbreviate this condition by x 6∈e. In the same way we will abbreviate for a given set of variable X, ∀x∈X, x6∈e by X 6∈e.
With this notation, the condition (†) reads:
x6∈ex
This means that once a variable x has been removed from an environment e with the function (−)−, xcan not be picked out anymore.
Returning to the N bE algorithm, the initialisation step for a term r consists in finding an environmenter such that thenew function will never give back a variable
among those free inr.
∀x∈FV(r), x6∈er
In fact for an arbitrary given e, eFV(r) does the job.
Example 3. This name generation environment has been implemented by Ulrich Berger in [17] with indexed variables, i.e., of the form xk where k ∈ N, and is essentially as follows.
The set of environment E is taken to be N, and a natural number k is a code for the sequence xk, xk+1, . . .. The initialisation part consists to look for the higher
index k of the variables of the form xk occurring in r. Because for all k0 > k+ 1,
xk0 is not free in r, kr =k+ 1 is a code for a sequence of fresh variables for r and
we only need to propagate a natural number instead of a set of variables. The implementation is
kxj ::= max(k, j+ 1)
new(k) ::=xk
kr ::= 0(FV(r))
In particular, the use of indexed variables by Berger corresponds to de Bruijn levels where an index of a variable corresponds to the number of lambda abstraction in the syntax tree of the term, from the occurrence of this variable to the root of the tree.
Remark 5. In an impure functional programming language, the concept of environ- ment, sequence of instructions and assignment are primitive. The implementation is then easy, it suffices to define such an e in the global environment and update it with an assignment instruction e := ex, which will update the environment before further computations.
However this would take us a step further away from a mathematical formalisa- tion. It is why we prefer to stick to a pure functional programming language setting, where the primitive notion of function has a direct counterpart in a mathematical setting. We will then implement impure functional concept such as environments (in the formalization section of chapter 2, 3 and 4) or exceptions (in the formalization section of chapter 3 and 4) within our functional settings with the help of monads.
Rest now to redesign the N bE algorithm to propagate in an adequate way this environment e through the computation.