Dependent types
August 27, 2014 (Wednesday) reddit thisIn the last post, we looked at simply typed lambda calculus where each term was associated with a type. As such the types of the language and terms were completely separated — we could define the types of the language independent of terms. A dependently typed language is one where the types can depend on values from a given type. For example consider the natural numbers . In a language like Haskell, we can define them inductively. However, what if we want to capture the collection of residue classes modulo a number . Ideally, we would want to distinguish the types and for each — the term should lead to a type error if and . A dependently typed language can supports construction of such types. For a type , a type family on is a collection of types one for each .
In the last post, we saw that the type inference rules of simply typed lambda calculus gave us the rules of natural deduction for (a fragment of) propositional logic via the Curry-Howard isomorphism. To extend this to predicate logic, we would need a dependently typed lambda calculus as we explain now. Recall that we think of types as mathematical statements and elements of that type as proofs of the statements. Therefore, to capture predicates on a type , we need a type for every , i.e. we need a type family on A. As before, to prove the statement should mean constructing .
Given a set and a predicate , which in the type theory world becomes a type and a type family , we need types that capture the logical formulae and . This is done through the dependent sum () and the dependent product () types respectively.
-types.
For a type and a type family , the dependent sum type (or -type) consists of all pairs where and . The motivation is that proving the statement for the set and predicate on constructively involves constructing a witness element for which is true. What more, in the constructive setting, being true has to be demonstrated by a proof . Thus elements of can be thought of as proofs of . Clearly if is not inhabited, then is not provable.
The ordinary product type (or in the last post) can be seen as the -type where is the type family that assigns to every in . We can also define the ordinary sum type (or in the last post) as the -type , is the boolean type containing two values and and is the type family .
The first component of any element of gives an element of . For types that do not have any inhabitants, it is impossible to construct element in . This is in line with the idea that is false.
-types.
The dependenent product (or the -type) consists of functions whose value is of type . The motivation from the logical side is that a proposition can be proved by giving a function that takes every element to a proof . In agda, the type is denoted by .
Dependent type languages gives ways to construct functions in for any empty type . For example, if is a type family we can create the function f in agda by using what is known as the absurd pattern (see line 2 below).
1 2 |
|
This is in line with the idea that is true.
The Vector
type: An example from programming.
We now give an example of the famous vector type in agda used to captures lists of a particular length.
1 2 3 |
|
Having defined such a type we can define the function head
as follows
1 2 |
|
One of the advantages of this definition of head
is that it can never be applied to an empty vector. This is because the input type Vector A (succ n)
of head
can never match with the type of the empty vector Vector A 0
and hence will lead to a compile time error.
Type inference rules.
We now give a sketch of the type inference rules for dependently typed lambda calculus. As before typing any term requires to “know” the type of all the free variables in it. We capture this by judgements of the kind . However, unlike in the simply typed case, might itself have variables embedded in it. So not just the free variables in but all free variables that occur in should appear in the type assumption. Thus our strategy of defining types independent of terms like in the previous post will not work; we need to define them hand in hand.
The type assumptions themselves should now be an ordered sequence of assumptions of the form with the added restriction that for any assumption in the sequence , all the variables that appear free in the type should themselves be defined in previous assumptions of . Therefore the order of the assumptions in matters and they cannot be treated as mere sets. Such sequences will be called telescopes of type assumptions. Finally, we can form expressions like only if is known to be a valid type which in turn depends on the telescope that is effective at that context. The inference rules for dependently typed lambda calculus thus needs to define simultaneously what the valid types, terms, telescopes and type judgements are. This we capture via the following judgements.
which asserts that is a valid term,
which asserts that is a valid telescope,
which asserts that is a valid type and finally
which asserts that the term is of type .
We use to denote the empty telescope. Any judgement means that the judgement is valid under the telescope .
We write as just or even . To reduce a lot of boiler plate, we sometimes drop certain preconditions if it can be deduced from the other preconditions. For example, we drop the precondition if we also have a precondition latter on.
- Rules for telescopes
- The first rule that we have is that an empty sequence is a valid telescope.
The next inference rule says that we can add an assumption at the end of a valid telescope provided it is possible to infer that is a type from the telescope .
We have slightly abused the notation in the expression of the side conditions which essentially says that is fresh, i.e. does not occur in any assumptions of .
- Formation rules for terms
- This essentially describes the syntax of our language.
The first two are essentially expressing the lambda calculus syntax for variables and application in the form of rules of inference. The last rule, however says that the expression is a valid term only if it is in the context of a telescope where is a type.
- Formation rules for -types
- We have the following rules by which we can form types
Notice that we omitted the precondition and as mentioned before.
- Rules for type inferences
- We have a single variable rule followed by the introduction/elimination rules for the -type.
To incorporate the rules for -types, we need to introduce a dependent pairing primitive and the corresponding pairing operations. We leave this as an exercise.
Where are the dependent types?
The dependently typed language we introduced here is mostly useless in the absence of any interesting types and type families. One strategy would be to introduce each type and type family by hand giving its, formation, introduction and elimination rules. At the very least, we fix a base set of types and add the formation rule This will give us all the types described in the previous post (once we identify with whenever is not free in ). To make dependently typed systems more useful actual systems supports construction of user defined types and type families. What these constructions are and what should be the restrictions on them has to be worked out. We defer this topic for the future.