In the previous section our typing rules did not have a typing context. With variables we must introduce one. Recall the definition:
g := {} | g,(xref,t)
Contexts grow to the right, ensuring that the most recently defined variable is at the end of the context. Lists in functional languages grow to the left:
data List a = Nil | Cons a (List a)
This means that a List context will have the most recently defined variable at the head of the list.
Idris supports SnocList,
a list datatype that grows to the right:
data SnocList a = Lin | Snoc (SnocList a) a
As we did with typing information,
we can also embed the typing context within our definition of Expr:
data Expr : (ctxt : SnocList Ty)
-> (type : Ty)
-> Type
If we revisit our judgement form for expressions (minus the global context):
g |- e : t
you will see that both Expr and our judgement have a context and a type.
The expression e,
will be the data constructors.
We have also, deliberately, decided to not include names in our context. But why?
Well, alpha equivalence tells us that two or more terms are equivalent if they share the same structure and uses of variables. If we do not include variable names then we can more easily compare terms for equality. But how do we represent variables?
De Buijn indexing is when we represent variables not by their names, but to the position in the context where their types are stored. We can use dependent types and list quantifiers to record where in a list an element occurs.
Consider the following dependent datatype Elem,
which is evidence that x is in sx:
data Elem : (x : type)
-> (sx : SnocList type)
-> Type
where
Here : (prf : x = y)
-> Elem x (sx :< y)
There : (ltr : Elem x sx)
-> Elem x (sx : y)
The type constructor for Elem states that we are establishing a proposition stating the relationship between an x and a SnocList sx.
The data constructors,
Here and There,
detail this relationship.
Here-
tells us that
xwill be at the ‘end’ of a SnocList, whereprfis definitional equality on values i.e.xandyare the same. There-
tells us that
xis not at theendof the list. The argumentltrstates thatxmust be found earlier within the list.
Elem is our proposition;
Our proof that Elem is both complete and sound comes from the following decision procedure:
isElem : DecEq type
=> (x : type)
-> (sx : SnocList type)
-> Dec (Elem x sx)
We will not explore isElem in detail,
aside from stating that you can find more information in Idris’ standard library.
Suffice it to say,
we use Elem as our nameless representation for variables.
Consider the following definition of Expr with boolean conjunction and variables.
data Expr : (ctxt : SnocList Ty)
-> (type : Ty)
-> Type
where
B : (b : Bool) -> Expr ctxt BOOL
And : (l : Expr ctxt BOOL)
-> (r : Expr ctxt BOOL)
-> Expr ctxt BOOL
Var : (idx : Elem type ctxt)
-> Expr ctxt type
Let : (this : Expr ctxt typeA)
-> (body : Expr (ctxt :< typeA) typeB)
-> Expr ctxt typeB
Var-
is our nameless variable representation,
stating that the expression will have type
typeiff there is proof (idx) thattypeexists in the context i.e. is bound. Let- introduces bound variables to the context by extending it.
We can now use Expr to write well-scoped expressions.
λΠ> Let (B True) (And (Var (Here Refl)) (B False))
Let (B True) (And (Var (Here Refl)) (B False))
λΠ> Let (B False) (Let (B True) (And (Var (Here Refl)) (B False)))
Let (B False) (Let (B True) (And (Var (Refl Here)) (B False)))
And attempt to write ill-scoped ones. For ill-scoped terms, we will write some code and get Idris to type check it:
example1 : Expr Lin BOOL
example1
= Let (B True) (And (Var $ There Here) (B False))
which results in:
While processing right hand side of example1. When unifying:
Expr (?sx :< BOOL) BOOL
and:
Expr [<] BOOL
Mismatch between: ?sx :< BOOL and [<].
Compare the definition of Var with the typing rule for local variables in Olaf/Ola.
Similarly compare the definition of Let with the typing rule for let-statements.
What do you notice about the translation of the rules from formal notation to Idris syntax?
Extend the definition of Expr to include support for integers and their addition.
For Expr to represent Ola expressions,
you will have to remove Let.