In this last section we will bring it all together, tackling how we can deal with method declarations and the boring bit of representing programs.

Intrinsically-Typed Methods and Method Calls

Recall from our syntax that a method call is an n-ary operation, and a method is a statement where there are n-ary inputs:

e := ... | mref({e_0,...,e_n) ...
m := (\{ xref_{0} : t_{0},..., xref_{n} : t_{n}} => s)

Unlike lambda calculi, method types are kept separate from the types of expressions. We can represent them in Idris using a separate type that captures the idea that a method can have many arguments, or none at all, and must return a value of a specific type.

data MTy = M (SnocList Ty) Ty

We have chosen a SnocList as we will see in the next lecture how this makes evaluation that little bit easier.

Before we start looking at creating a representation for methods themselves, we have to remember that methods are globally declared and are referenced in method calls! Thus, we need to first deal with method calls and the idea that we have a global context for methods.

We can use our knowledge of intrinsically-typed expressions to extend our definitions for Expr and Stmt with a global context for methods. We will, however, cheat a little. The judgement forms for expressions and statements are almost the same.

Expressions Statements
g;l |- e : t g;l |- s : t

One types expressions, the other statements. We can use this knowledge, and the power of dependent types, to make life easier when constructing terms. We will create a type-synonym to capture the common judgement forms:

SPEC : Type
SPEC = (global : SnocList MTy)
    -> (local  : SnocList Ty)
    -> (type   : Ty)
              -> Type

Our type constructors for expressions and statements are now:

data Expr : SPEC where

data Stmt : (he : HowEnds) -> SPEC where

Rewrite your definitions of Expr and Stmt to use the new signatures.

Method Calls

We now need to type method calls. Which has the formal rules:

g;l |- mref : {t_{0},...,t_{n}} -> t
g;l |- e_{0} : t_{0}
...
g;l |- e_{n} : t_{n}

---- [ Method Call ]

g;l |- mref({e_{0},...,e_{n}})) : t

We know that variables are typed as

Var : (idx : Var    type local)
          -> Expr global local type

Method calls will be similar as we need to obtain the method type from the global context. Thus, idx will be:

(idx : Var (M tyArgs tyRet) global)

but what about typing the arguments themselves? To do so we need to introduce the All quantifier which we use for reasoning about lists but is good for collecting dependently-typed values.

data All : (p  : (x : type) -> Type)
        -> (sx : SnocList type)
              -> Type
  where
    Lin : All p Lin
    (:<) : (front : All p sx)
        -> (last  : p x)
                 -> All p (sx :< x)

The type constructor for All states that we are applying a predicate p over a list of values sx.

The two constructors, Lin for the empty list and (:<) for extending the list backwards, ensures that all elements within sx satisfies the predicate p.

We can use this to ensure that all expressions, presented as arguments, satisfy the method’s type signature. Specifically:

Call : (idx  : Var (M tyArgs tyRet) global)
    -> (args : All (Expr global local) tyArgs)
            -> Expr global local tyRet

With this, we can now type methods themselves.

Extend your definition of Exprwith Method calls.

Intrinsically-Typed Methods

Recall the typing rule for Methods:

g, { (xref_{0}, t_{0}),..., (xref_{n}, t_{n})} |- s : t

---- [ Method ]

g |- (\{ xref_{0} : t_{0},..., xref_{n} : t_{n}} => s)
       : { t_0,..., t_n } -> t

The interesting aspect of the rule is that the method’s arguments populate the local stack for typing statements. We can use this information to help construct a dependent datatype for methods. We must also note that methods do not have a local context.

The type constructor for methods follows their judgement form from the typing rules.

data Method : (global : SnocList MTy)
           -> (type   : MTy)
                     -> Type
  where

The single term, the method itself, adheres to the rules by ensuring that the type signature for the method types a statement correctly. That is, the arguments are the statement’s initial local typing context and the method’s return type is the return type from the method type signature. Importantly, we fix how the statement ends, using RETURN to ensure that the method itself returns a value of type tyRet.

    M : (body : Stmt RETURN global       tyArgs tyRet)
             -> Method      global (METH tyArgs tyRet)

Typing Programs.

We end this section, and the lecture, by constructing intrinsically-typed programs.

As a reminder, here is the syntax we need to represent:

mdecl := mref = m

p := m --- main method
  | mdecl ; p

We begin with the judgement form which has a global context for methods.

data Prog : (global : SnocList MTy) -> Type where

We can now proceed to write our terms. As global declarations are analogous to let-binders, we can use our knowledge of binders from earlier to type our declarations. First, we give the formal specification.

g             |- mref = m : mty
g, (mref,mty) |- p

---- [ Method Declarations]

g |- mref = m; p

As we are in a nameless context we do not need to keep track of the names, and like binders, we extend the context for the rest of the program with the type of the thing being bound. For programs, this will be methods.

Decl : (ds   : Method  global    m)
    -> (rest : Prog   (global :< m))
            -> Prog    global

The final rule is that for the main method which, for Olaf/Ola, is a method with no arguments and returns ‘nothing’.

g |- m : {} -> unit

---- [ Main Method]

g |- m

For the main method, we need to ensure that the type of the method is: {} -> unit.

Et voila:

Main : Method global (METH Lin UNIT)
    -> Prog   global

With this last term we have complete the lecture.

For the sample programs in walk-through for Olaf represent them using Prog.

Many imperative languages have main methods that take in system arguments and return values of type int. Rewrite Prog to support such main methods.