With dependent-types, types can depend on values. When combined with algebraic datatypes, which in Idris are indexed families, we can construct much more expressive datatypes descriptions.

Using these expressive datatypes we can start to encode typing information within our ADTs representing our ASTs. Let us revisit our little expression language of booleans, and integers:

t := bool | int -- types
c := b | i      -- constants
e := (add e e)
   | (and e e)

With types encoded as:

data Ty = BOOL | INT -- Types

We want to state that expressions will have a type: e : t.

We do this by indexing our datatype for ASTs, Expr, with typing information. Here is the new type constructor for Expr:

data Expr : (type : Ty) -> Type where

Expr is our first dependent datatype and takes as an argument, an instance of our datatype representing types. Meaning that when specifying our data constructors (i.e. specifying terms) we can add the terms typing information.

We will show this by looking at the introduction rules for Booleans.

---- [ Intro Bool ]
b : Bool

The data constructors will look like:

True  : Expr BOOL
False : Expr BOOL

The return type, Expr BOOL, is our conclusion. With this example, we have explicitly represented boolean values as data constructors. While okay for booleans, we cannot use this approach for infinite values such as integers and strings. We saw with ASTs, that we can wrap primitive values as arguments. We will use this form for primitives:

B : (b : Bool) -> Expr BOOL

Let us now look at rules containing premises. Specifically, the typing rule for integer addition:

a : int
b : int

---- [ Addition ]

add(a,b) : int

Each premise will be translated into an argument for the data constructors. Meaning that each premise is an inductive call that must have some typing information.

Our rule for add, is represented as:

Add : (a : Expr INT)
   -> (b : Expr INT)
        -> Expr INT

Add has two premises, each operand which must have type INT, and the conclusion ensuring that Add expressions also have type INT.

Using the following typing rules, for introducing integers and boolean conjunction, extend the definition of Expr with constructors I and And

---- [ Intro Int ]
i : INT
a : bool
b : bool

---- [ Conjunction ]

and(a,b) : bool

Now let us see what happens when we try and construct terms using Idris’ REPL:

λΠ> Add (I 1) (B True)
Error: When unifying:
    Expr BOOL
and:
    Expr INT
Mismatch between: BOOL and INT.

(Interactive):1:12--1:18
 1 | Add (I 1) (B True)
                ^^^^^^
λΠ> And (B False) (B True)
And (B False) (B True)

Idris will reject all ill-typed terms and accept well-typed ones!

In the last section, you were asked to write both ill-and-well-typed terms. Try writing them again but using the new version of Expr. What happens?

The expressions we have seen thus far are irreducible. In the next section, we will look at irreducible terms.