In -calculus, a binder is a abstraction that introduces a new variable. so for example introduces a variable hence is a binder for all the that are in . To convert a named lambda term into a deBrujin indexed nameless expression, for each variable, we have to replace it with the (nearest) distance of the binder that binds this variable. So if the nearest binder of is THE nearest binder, we give it an index , if the nearest binder of is the second near binder, we give it an index and so forth.
So, let's write a named expression in deBrujin form:
consider
for each variable, we have to find the nearest lambda on the left that binds it. First of all, is the outermost and only binder for . if we notice closely, the above expression looks something like , that is we are talking about a function that takes z as the argument and returns an application . which means that any in or are bound by this outer lambda. Notice , so binds , and in bind . hence, the nearest binding of is two levels deep, (and using zero indexing) the index of is hence .
using this logic over and over again, seeing the nearest lambda that binds a variable, and how deep it is, we can re-write the expression in deBrujin form.
how do we convert it back? it's pretty straight forward, we just give each binder a variable name, and use distances given by the indices to re-name the indices.
but now we have the mysterious shift operator:
upShift
for any nameless term , the upshift of by with cutoff , written is defined as follows:
if is an index: if then . if then .
if t is a lambda abstraction then .
if t is an application then .
Substitution:
The rough idea of substitution is that functions are only dependent on how the transform an input. Not on what the inputs are named or what they look like. So we want to have some rules to re-write variables in such a way that a re-write (or substitution) is functionally equivalent to its original.
usually, in named form, a lambda abstraction looks like , where is any term. in nameless (deBrujin index) form, . in any even, we see that all that appear in are bound by the outer lambda. similarly, in the deBrujin form , there might be indices who's nearest binding is in fact this outer lambda.
The key thing to internalize is that we are not substituting a variable NAME anymore. deBrujin indices are not just names. They represents distances from the nearest lambda that binds them.
but there are more issues here:
Let us look at some substitutions in the case of named expressions.
for example our result is which is quite funny, as it changes the identity function into a constant function.
Hence, it should be a rule that once we encounter a variable name in binding,we can't substitute it. so we account for that.
Next, consider : this results in . This time we morphed a constant function into the identity function. In the general substitution , the free variables in becoming bound when substituting in is called "capture". let's correct that.
Whenever the above definition doesn't give us an answer, we can use alpha conversion to fully re-name all bound variables appropriately.
Now, we need to figure out the analog of the substitution for deBrujin expressions.
In order to do this, we need to develop deBrujin expressions a little more.
The first thing we need is the definition of a term
Term
a term is an element of : the smallest family of sets such that:
An index is in if
If a term and ,
if terms then
Essentially, each represents the set of all terms that have at most free-variables.
when we were talking about binders, we knew how to set index of a variable, by finding the distance of the nearest binder that binds that variable. What about free variables? well, for that, we need a context.
Context
let be the set of variable names in named lambda calculus.
The context select names from and assigns them indices respectively. (),
The rough idea is that having an length context allows us to write any term in cause we do have free variables.
Let's setup a context for our free variables: .
now, lets try to convert into deBrujin form. we know that the nearest binder to is at distance , so but as well? NOT SO FAST! the moment we go inside the "context" of a binder, , we know that can appear in the body such that is it's nearest binder and at a distance . so this means that inside the context of this binder, all our indices free variables must be incremented.
so and , hence written as .
So now, we can build up our Substitution in deBrujin form.
Remember that we can only substitute free variables. let be an length context. let be the index of a free variable in that we want to replace by a term , in the term .
so this lambda binds some index. whatever it binds, assuming there is no more 's in , that bound index is . and inside the lambda, the free variable is actually . Moreover, in , we need to ensure all the free variables are incremented, so we need to upshift it.
The cutoff in the upshift operator makes a lot of sense right now. suppose , then of course, the context inside the lambda (ie in ) has all it's free variables indices from to instead of to in the outer context . so if we want to upshift all the free variables in
we should up shift variables in and ignore as the lambda binds it.
Therefore, it makes sense that .
So now, we finally have made it.
Substitution in DeBrujin expressions
Let be an length context for free variables, and (notice contains for ). Then the substitution of a free index with the term in the term is defined recursively as follows:
If is an index,
if :
if :
The great beta reduction:
So, here we are, and now we want to write the reduction as a substitution in deBrujin expressions.
so we are given a context of length , , and a lambda abstraction , a term and we need to evaluate which is the beta reduction. so in a beta reduction, we always substitute the index bound by the outermost lambda. It is pretty safe to say that we are substituting the index in with . But there is a slight catch. if , then we see that the context of free variables in is upshifted because it is inside a lambda.
but the context of free variables, is still the global context . so we need to ensure that we upshift before substituting.
so we have something along the lines of . The second issue is, that the beta reduction actually removes the outermost lambda, which means that it unbinds some variable. Which then means that we need to downshift the context.
so the final beta reduction is .
jumping through hoops, the kth beta reduction:
beta reduction
let be deBrujin terms in some context . The beta reduction of at is: $$ \uparrow^{-1}[0 \mapsto \uparrow^1v]b $$
the beta reduction is (denoted as ):
Lets try to notice something, suppose . Try simplify the expression :
Wow! if has a chain of lambdas, then then the beta reduction of at is equivalent to
Version space:
Version space
a version space is one of the following:
An index
An abstraction where is a version space
An application where are version spaces.
A union where is a set of version spaces.
The set of all lambda expressions
The empty set
We are back to the paper now, and are again in thinking about our deBrujin expressions as programs. Now basically, a version space for a program, efficiently and compactly maintains refactorings of a program. (we will explore this in detail later)
Now, we want to define an operator that takes in version spaces and spits out sets of "substitutions" between two version spaces (note that are version spaces).
But these substitutions are not the substitutions defined earlier, the idea is that these represent refactorings of programs in the version space . in particular if and are programs, then the beta reduction of at : is a program in . (a notion of consistency)
Moreover, if and are programs, and we have that , then it must mean that there are version spaces such that and . (a notion of completeness)
But as is the nature of inductive definitions, we actually want to define . TAKE THE DOWNSHIFT OPERATOR NOW!
downshift
We can think of shifting operations applied to version spaces (which are basically sets of programs) as applying it to to each program in that set.
kth refactor operator
Lemma 1. Let e be a program or version space and n, c be natural numbers. Then You can't use 'macro parameter character #' in math mode\uparrow_{n+c}^{-1} \uparrow_c^{n+1} e=\uparrow_c{#n} e, and in particular .
Lemma 2. Let e be a program or version space and n, c be natural numbers. Then , and in particular .
Consistency of
If , then for all programs and , the beta reduction of at , .
We first deal with the case that . in this case, You can't use 'macro parameter character #' in math modea = \downarrow_{0}{#kv} let be any program in . we know that arbitrary can be written as for some . In particular, . moreover, . hence: $$ \uparrow_{k}^{-1}[k \mapsto \uparrow^{k+1}a']b' = \uparrow_{k}^{-1}[k \mapsto \uparrow^{k+1}\downarrow_{0}^kv']k = \uparrow^{-1}_{k}(\uparrow^{k+1}\downarrow^kv')$$
invoking the above lemmas, we notice that the rightmost expression evaluates to .
Now, for the other cases, we use structural induction:
if and , we have to deal with and . pick any program . we want to evaluate $$\uparrow_{k}^{-1}[k \mapsto \uparrow^{k+1}a']i = i$$ (because ) and we are done.
if and , we deal with and . pick arbitrary , we get:
again inductively, and such that . then by definition of , .
if is a primitive, then we have . but and hence can't be substituted, and not being an index also doesn't help. Therefore, hence , and so we good.
Finally if is a union of version spaces, apply the hypothesis of the theorem inductively to the version spaces inside v, and so forth. If is empty or the theorem holds vacuously.