2.2 Typing
2.2.4 Datatypes
The rules for datatypes and pattern matching appear in Figure 2.7. Datatype decla- rations have the form:
dataD∆ :Type`where{diof∆i i∈1..k
}
Here, D is the type being introduced. The telescope ∆ contains its parameters. Its term constructors are the di and their arguments are ∆i.
Datatype declarations are checked by the context well-formedness judgement `Γ (Figure 2.7). Its rule CData handles datatypes. The first two premises use the auxiliary judgement Γ ` ∆ : Type` to ensure that the types of the type constructor and term constructors are themselves well typed and reside at the appropriate universe
Γ`θa:A
dataD∆ :Type`where{diof∆i i∈1...j } ∈Γ Γ`LAii: ∆ `Γ Γ`LDA i i :Type` TTCon dataD∆ :Type`∈Γ Γ`LAii: ∆ `Γ Γ`θDA i i :Type` TATCon
dataD∆ :Type`where{diof∆i i∈1...j } ∈Γ Γ`LAii : ∆ `Γ Γ`θaii: [Aii/∆]∆ k Γ`θd kaii:DAi i TDCon Γ`La:Daii Γ`LB:Type `2
dataD∆ :Type`1where{diof∆i i∈1...k } ∈Γ ∀i∈1...k, Γ,[aii/∆]∆iθ,y :La=di∆i `θbi:B Γ`θcaseaof{d i∆i ⇒bi i∈1...k }:B TCase `Γ `. CNil Γ`LA:Type ` x ∈/dom(Γ) `Γ `Γ,x :θA CVar Γ`∆ :Type`
∀i ∈1...k, Γ,dataD∆ :Type`,∆L`∆i:Type`
∀i ∈1...k, D’s occurances in∆i are strictly positive D ∈/dom(Γ) di ∈/dom(Γ)
i∈1...k `Γ `Γ,dataD∆ :Type`where{diof∆i
i∈1...k } CData Γ`∆ :Type` Γ` ·:Type` TeleWFNil Γ`LA:Type ` Mob(A) Γ,x :LA`∆ :Type ` Γ`(x :A)∆ :Type` TeleWFCons Γ`θa ii: ∆ `Γ Γ`θ·:· TeleNil Γ`θa:A Γ`θaii: [a/x]∆ Γ`θaaii : (x :A)∆ TeleCons
level. In the latter case, we add two declarations to the context. First, we add an abstract version of the datatype being defined (since the type constructor should be available when checking the types of the term constructors, but the term constructors themselves should not). Second, we add the parameters to the type constructor— these can be thought of as implicit arguments to each term constructor. Note that all arguments to type and term constructors are required to have mobile types. This is the reason datatypes are mobile themselves. The CData rule also ensures that recursive uses of the type constructor are strictly positive, a standard requirement which is necessary to ensure the consistency of the logical fragment. We omit the technical definition of strictly positive, but it can be found in the literature [40].
Our representation of dependent datatypes is somewhat unusual in that it does not explicitly include “indices”—arguments to the type constructor that may vary in the result type of each data constructor. We have chosen this representation for simplicity. It is still possible to encode invariants for which indices are typically used. Rather than having a constructor’s return type instantiate a parameter, one may add an extra argument to the constructor assuming that the relevant parameter is equal to some other term. For example, to encode a length-indexed vector type with a natural-number parameter x, the nil constructor would take an extra argument of type x = 0, and the cons constructor would take two extra arguments: the length y of the tail and a proof that x = Succ y. This example will be considered in greater detail in Section 2.3.1.
Term and type constructors are looked up from the context with rules TDCon, TTCon and TATCon. For type constructors, rules TTCon and TATCon simply look up the constructor in the context and ensure that the arguments to which it is applied correspond to its declared telescope of parameters, using the auxiliary judgement Γ`θ a
ii : ∆.
For term constructors, the situation is slightly more complicated. In most de- pendently typed languages, if d is a constructor of type D∆, the parameters ∆ are arguments to d as well, since they appear in d’s return type. For example, in Coq and Agda, the typeListAhas constructorsniland conswhich take the typeAof data contained in the list as an argument. However, rather than actually requiring dto be applied explicitly to an appropriate instantiation of these variables, the rule TDCon simply checks that a suitable instantiation of ∆ exists. Of course, in practice, this is undecidable and these arguments must often be supplied explicitly in a surface language. But by leaving them out of the term constructor applications in the core language, we prevent type information from getting in the way of equalities.
Pattern matching expressions have the form:
caseaof{di∆i ⇒bi i∈1...k
}
Here we are using a telescope to represent the list of bound variables—this is the pattern being matched against. These expressions are checked by rule TCase. The first two premises check that the scrutinee a is a member of some datatype Daii
and that the match’s return type B is well-formed. The third premise ensures that D is defined in the context. Note that we don’t require any relationship between the universe level of B and the universe level of the datatype D. In particular, this permits large eliminations, which are types defined by pattern matching on values. Large eliminations are discussed in greater detail in Section 6.6.
The final premise checks the individual clauses of the pattern match. Since the parameters of the datatype are not arguments to each constructor, users do not match against them. Instead, when we extend the environment with the rest of the arguments to the appropriate constructor, we substitute the scrutinee’s instantiation ofD’s parameters into the types of di’s arguments. When checking a branchdi∆i ⇒
bi, we also extend the context with a proof that a =di∆i (i.e., that the scrutinee is equal to the pattern being matched against).
Readers familiar with dependent pattern matching might be surprised that the final premise of this rule does not perform any substitutions in the return type of the match. Since indices are represented by adding equality hypotheses to the constructor types, these will be available to the type system for rewriting in each branch, along with the proof that the scrutinee is an application of the appropriate constructor. While this might be inconvenient in practice, we believe that a more standard pattern matching construct in the surface language could be compiled into our explicit version.