Implementing Dependent Types in Haskell

Implementing Dependent Types in Haskell
Andres Löh1
joint work with Conor McBride2 and Wouter Swierstra2
1 Universiteit
2 University
Utrecht
of Nottingham
September 12, 2007
Motivation
Present a type checker for dependent types, implemented in Haskell.
Only a core language as a basis for experimentation:
much like Fω is for Haskell/GHC.
There are many design choices:
Keep it simple . . .
. . . yet powerful enough to demonstrate some of the advantages gained
by dependent types.
For programmers interested in type systems, not type theorists
interested in programming.
Andres Löh
Implementing Dependent Types in Haskell
2
Why dependent types?
Lots of type-level programming in Haskell: more static guarantees,
but
duplication of concepts on different layers
more and more type system extensions
some of them with restrictions and metatheory that is difficult to
understand
Andres Löh
Implementing Dependent Types in Haskell
3
Why dependent types?
Lots of type-level programming in Haskell: more static guarantees,
but
duplication of concepts on different layers
more and more type system extensions
some of them with restrictions and metatheory that is difficult to
understand
Dependent types offer:
type-level programming becomes term-level programming
programs, properties, and proofs within a single formalism
a comparatively clean theory on the surface
of course, there’s another set of problems, but . . .
Andres Löh
Implementing Dependent Types in Haskell
3
Simply-typed Lambda Calculus λ→
t ::= a | t → t0
Andres Löh
Implementing Dependent Types in Haskell
4
Simply-typed Lambda Calculus λ→
t ::= a | t → t0
e ::= e :: t | x | e1 e2 | λx → e
Andres Löh
Implementing Dependent Types in Haskell
4
Simply-typed Lambda Calculus λ→
t ::= a | t → t0
e ::= e :: t | x | e1 e2 | λx → e
Γ ::= ε | Γ, a :: ∗ | Γ, x :: t
Andres Löh
Implementing Dependent Types in Haskell
4
Simply-typed Lambda Calculus λ→
t ::= a | t → t0
e ::= e :: t | x | e1 e2 | λx → e
Γ ::= ε | Γ, a :: ∗ | Γ, x :: t
valid(Γ)
valid(Γ) Γ ` t :: ∗
valid(ε) valid(Γ, a :: ∗)
valid(Γ, x :: t)
Andres Löh
Implementing Dependent Types in Haskell
4
Simply-typed Lambda Calculus λ→
t ::= a | t → t0
e ::= e :: t | x | e1 e2 | λx → e
Γ ::= ε | Γ, a :: ∗ | Γ, x :: t
valid(Γ)
valid(Γ) Γ ` t :: ∗
valid(ε) valid(Γ, a :: ∗)
valid(Γ, x :: t)
Γ(a) = ∗ Γ ` t :: ∗ Γ ` t0 :: ∗
Γ ` a :: ∗
Γ ` t → t0 :: ∗
Andres Löh
Implementing Dependent Types in Haskell
4
Simply-typed Lambda Calculus λ→
t ::= a | t → t0
e ::= e :: t | x | e1 e2 | λx → e
Γ ::= ε | Γ, a :: ∗ | Γ, x :: t
valid(Γ)
valid(Γ) Γ ` t :: ∗
valid(ε) valid(Γ, a :: ∗)
valid(Γ, x :: t)
Γ(a) = ∗ Γ ` t :: ∗ Γ ` t0 :: ∗
Γ ` a :: ∗
Γ ` t → t0 :: ∗
Γ ` t :: ∗ Γ ` e ::↓ t
Γ ` (e :: t) ::↑ t
Γ(x) = t
Γ ` x ::↑ t
Γ ` e ::↑ t
Γ ` e ::↓ t
Andres Löh
Γ ` e1 ::↑ t → t0 Γ ` e2 ::↓ t
Γ ` e1 e2 ::↑ t0
Γ, x :: t ` e ::↓ t0
Γ ` λx → e ::↓ t → t0
Implementing Dependent Types in Haskell
4
Evaluation in λ→
v ::= n | λx → v
n ::= x | n v
Andres Löh
Implementing Dependent Types in Haskell
5
Evaluation in λ→
v ::= n | λx → v
n ::= x | n v
e⇓v
e :: t ⇓ v
e1 ⇓ λx → v1 e2 ⇓ v2
e1 e2 ⇓ v1 [x 7→ v2 ]
Andres Löh
x⇓x
e1 ⇓ n1 e2 ⇓ v2
e1 e2 ⇓ n1 v2
e⇓v
λx → e ⇓ λx → v
Implementing Dependent Types in Haskell
5
Moving to dependent types: dependent functions
The construct
∀x :: t.t0 (often also written Π x :: t.t0 )
generalizes and thereby replaces the function arrow
t → t0
Difference: x may occur in t0 . If it doesn’t, we just write t → t0 as
syntactic sugar.
Note: This also generalizes (Haskell’s) parametric polymorphism, but we
do not enforce parametricity.
Andres Löh
Implementing Dependent Types in Haskell
6
Moving to dependent types: everything is a term
We collapse the multi-level structure (terms, types, [kinds]) – everything is
a term. The ∀ moves to the term-level, in turn lambda abstraction and
application become available to (former) types.
The symbol
::
becomes a relation between two terms.
Computation arrives in the world of the types.
Also automatically introduces “kinds”.
Andres Löh
Implementing Dependent Types in Haskell
7
Example
We can state a large class of properties as types:
∀(a :: ∗) (xs :: List a).reverse (reverse xs) = = xs
∀(x :: Nat) (xs :: List Nat).
Holds (sorted xs)
→ Holds (sorted (insert x xs))
An inhabitant of such types is a proof (Curry-Howard).
Consistency of the type system is an advantage.
Andres Löh
Implementing Dependent Types in Haskell
8
Conversion rule
Computation on types also introduces a problem: When are two types
equal?
Vec (2 + 2) Nat = Vec 4 Nat
Vec (x + 0) Nat = Vec x Nat
(assuming x :: Nat in the context)
Vec (x + y) Nat = Vec (y + x) Nat (assuming x, y :: Nat in the context)
Conversion rule:
Γ ` e :: t0 t = t0
Γ ` e :: t
In our case: evaluate both terms to normal form, then compare for
(alpha-)equality.
We try to keep the calculus strongly normalizing.
Andres Löh
Implementing Dependent Types in Haskell
9
Dependently-typed Lambda Calculus λΠ
e, t ::= e :: t | ∗ | ∀x :: t.t0 | x | e1 e2 | λx → e
Andres Löh
Implementing Dependent Types in Haskell
10
Dependently-typed Lambda Calculus λΠ
e, t ::= e :: t | ∗ | ∀x :: t.t0 | x | e1 e2 | λx → e
Γ ::= ε | Γ, x :: t
Andres Löh
Implementing Dependent Types in Haskell
10
Dependently-typed Lambda Calculus λΠ
e, t ::= e :: t | ∗ | ∀x :: t.t0 | x | e1 e2 | λx → e
Γ ::= ε | Γ, x :: t
valid(Γ) Γ ` t ::↓ ∗
valid(ε)
valid(Γ, x :: t)
Andres Löh
Implementing Dependent Types in Haskell
10
Dependently-typed Lambda Calculus λΠ
e, t ::= e :: t | ∗ | ∀x :: t.t0 | x | e1 e2 | λx → e
Γ ::= ε | Γ, x :: t
valid(Γ) Γ ` t ::↓ ∗
valid(ε)
valid(Γ, x :: t)
Γ(a) = ∗ Γ ` t :: ∗ Γ ` t0 :: ∗
Γ ` a :: ∗
Γ ` t → t0 :: ∗
Andres Löh
Implementing Dependent Types in Haskell
10
Dependently-typed Lambda Calculus λΠ
e, t ::= e :: t | ∗ | ∀x :: t.t0 | x | e1 e2 | λx → e
Γ ::= ε | Γ, x :: t
valid(Γ) Γ ` t ::↓ ∗
valid(ε)
valid(Γ, x :: t)
Γ(a) = ∗ Γ ` t :: ∗ Γ ` t0 :: ∗
Γ ` a :: ∗
Γ ` t → t0 :: ∗
Γ ` t ::↓ ∗ Γ ` e ::↓ t
Γ ` (e :: t) ::↑ t
Γ ` ∗ ::↑ ∗
Γ(x) = t
Γ ` x ::↑ t
Γ ` e1 ::↑ ∀x :: t.t0 Γ ` e2 ::↓ t
Γ ` e1 e2 ::↑ t0 [x 7→ e2 ]
Γ ` e ::↑ t0 t ⇓ v
Γ ` e ::↓ t
Andres Löh
Γ ` t ::↓ ∗ Γ, x :: t ` t0 ::↓ ∗
Γ ` ∀x :: t.t0 ::↑ ∗
t0 ⇓ v
Γ, x :: t ` e ::↓ t0
Γ ` λx → e ::↓ ∀x :: t.t0
Implementing Dependent Types in Haskell
10
Evaluation in λΠ
v ::= x v | ∗ | ∀x :: v.v0 | λx → v
Andres Löh
Implementing Dependent Types in Haskell
11
Evaluation in λΠ
v ::= x v | ∗ | ∀x :: v.v0 | λx → v
e⇓v
e :: t ⇓ v
Andres Löh
∗⇓∗
t ⇓ v t0 ⇓ v 0
∀x :: t.t0 ⇓ ∀x :: v.v0
x⇓x
Implementing Dependent Types in Haskell
11
Evaluation in λΠ
v ::= x v | ∗ | ∀x :: v.v0 | λx → v
e⇓v
e :: t ⇓ v
∗⇓∗
e1 ⇓ λx → v1 e2 ⇓ v2
e1 e2 ⇓ v1 [x 7→ v2 ]
Andres Löh
t ⇓ v t0 ⇓ v 0
∀x :: t.t0 ⇓ ∀x :: v.v0
e1 ⇓ n1 e2 ⇓ v2
e1 e2 ⇓ n1 v2
x⇓x
e⇓v
λx → e ⇓ λx → v
Implementing Dependent Types in Haskell
11
Implementation in Haskell
Abstract Syntax
Evaluation
Substitution
Typechecking
Quotation
Andres Löh
Implementing Dependent Types in Haskell
12
Abstract Syntax
data Term↑
= Ann Term↓ Term↓
| Star
| Pi Term↓ Term↓
| Var Int
| Par Name
| Term↑ :@: Term↓
deriving (Show, Eq)
data Term↓
= Inf Term↑
| Lam Term↓
deriving (Show, Eq)
Andres Löh
Implementing Dependent Types in Haskell
13
Contexts, Values
type Type
= Value
type Context = [(Name, Type)]
data Value
= VLam (Value → Value)
| VStar
| VPi Value (Value → Value)
| VNeutral Neutral
data Neutral
= NPar Name
| NApp Neutral Value
Andres Löh
Implementing Dependent Types in Haskell
14
Evaluation
eval↓ :: Term↓ → Env → Value
eval↑ :: Term↑ → Env → Value
Andres Löh
Implementing Dependent Types in Haskell
15
Typechecking
type↑ :: Int → Context → Term↑ → Result Type
type↓ :: Int → Context → Term↓ → Type → Result ()
type↑ i Γ (Ann e t)
= do type↓ i Γ t VStar
let v = eval↓ t [ ]
type↓ i Γ e v
return v
type↑ i Γ Star
= return VStar
Andres Löh
Implementing Dependent Types in Haskell
16
Typechecking, continued
type↑ :: Int → Context → Term↑ → Result Type
type↓ :: Int → Context → Term↓ → Type → Result ()
type↑ i Γ (Pi t t0 )
= do type↓ i Γ t VStar
let v = eval↓ t [ ]
type↓ (i + 1) ((Bound i, v) : Γ)
(subst↓ 0 (Par (Bound i)) t0 ) VStar
return VStar
subst↑ :: Int → Term↑ → Term↑ → Term↑
subst↓ :: Int → Term↑ → Term↓ → Term↓
Andres Löh
Implementing Dependent Types in Haskell
17
Typechecking, continued
type↓ i Γ (Inf e) v
= do v0 ← type↑ i Γ e
unless (quote0 v = = quote0 v0 ) (throwError "type mismatch")
Andres Löh
Implementing Dependent Types in Haskell
18
Typechecking, continued
type↓ i Γ (Inf e) v
= do v0 ← type↑ i Γ e
unless (quote0 v = = quote0 v0 ) (throwError "type mismatch")
quote0 :: Value → Term↓
quote0 = quote 0
quote :: Int → Value → Term↓
Andres Löh
Implementing Dependent Types in Haskell
18
Typechecking, continued
type↓ i Γ (Inf e) v
= do v0 ← type↑ i Γ e
unless (quote0 v = = quote0 v0 ) (throwError "type mismatch")
quote0 :: Value → Term↓
quote0 = quote 0
quote :: Int → Value → Term↓
Example:
quote 0 (VLam (λx → VLam (λy → x)))
= Lam (quote 1 (VLam (λy → vpar (Unquoted 0))))
= Lam (Lam (quote 2 (vpar (Unquoted 0))))
= Lam (Lam (neutralQuote 2 (NPar (Unquoted 0))))
= Lam (Lam (Var 1))
Andres Löh
Implementing Dependent Types in Haskell
18
Where are the dependent types?
Total implementation is about 100 lines of Haskell code.
Easy to see that we have gained advantages compared to λ→ .
Hard to actually use the power of dependent types without adding
datatypes to the language.
Andres Löh
Implementing Dependent Types in Haskell
19
Adding datatypes
Add the type.
Add the constructors (introduction forms).
Types
Add constructors to values.
Add an eliminator (eliminator forms).
Type
Add evaluation rules for eliminator.
Andres Löh
Implementing Dependent Types in Haskell
20
Natural numbers
e ::= · · · | Nat | Zero | Succ e | natElim e e e e
v ::= · · · | Nat | Zero | Succ v
n ::= · · · | natElim v v n
Andres Löh
Implementing Dependent Types in Haskell
21
Evaluation
Nat ⇓ Nat
Andres Löh
Zero ⇓ Zero
k⇓l
Succ k ⇓ Succ l
Implementing Dependent Types in Haskell
22
Evaluation
Nat ⇓ Nat
Zero ⇓ Zero
mz ⇓ v
natElim m mz ms Zero ⇓ v
Andres Löh
k⇓l
Succ k ⇓ Succ l
ms k (natElim m mz ms k) ⇓ v
natElim m mz ms (Succ k) ⇓ v
Implementing Dependent Types in Haskell
22
Typing
Γ ` Nat :: ∗ Γ ` Zero :: Nat
Andres Löh
Γ ` k :: Nat
Γ ` Succ k :: Nat
Implementing Dependent Types in Haskell
23
Typing
Γ ` Nat :: ∗ Γ ` Zero :: Nat
Γ ` k :: Nat
Γ ` Succ k :: Nat
Γ ` m :: Nat → ∗
Γ, m :: Nat → ∗ ` mz :: m Zero
Γ, m :: Nat → ∗ ` ms :: ∀k :: Nat.m k → m (Succ k)
Γ ` n :: Nat
Γ ` natElim m mz ms n :: m n
Andres Löh
Implementing Dependent Types in Haskell
23
Eliminator vs. fold
natFold :: ∀m :: ∗
.
natElim :: ∀m :: Nat → ∗.
m
→ (m → m)
→ Nat → a
m Zero
→ (∀k :: Nat.m k → m (Succ k))
→ ∀n :: Nat.m n
natFold r zero succ = natElim (λ → r) zero (λ rec → succ rec)
Andres Löh
Implementing Dependent Types in Haskell
24
Addition
plus = natElim (λ → Nat → Nat)
(λn → n)
(λ rec n → Succ (rec n))
Andres Löh
Implementing Dependent Types in Haskell
25
Implementation in Haskell
Systematically extend all the functions . . .
Andres Löh
Implementing Dependent Types in Haskell
26
Vectors
Vectors are lists that keep track of their length.
Vec :: ∀(a :: ∗) (n :: Nat). ∗
Andres Löh
Implementing Dependent Types in Haskell
27
Vectors
Vectors are lists that keep track of their length.
Vec :: ∀(a :: ∗) (n :: Nat). ∗
Nil :: ∀a :: ∗.Vec a Zero
Cons :: ∀a :: ∗.∀n :: Nat.a → Vec a n → Vec a (Succ n)
Andres Löh
Implementing Dependent Types in Haskell
27
Vectors
Vectors are lists that keep track of their length.
Vec :: ∀(a :: ∗) (n :: Nat). ∗
Nil :: ∀a :: ∗.Vec a Zero
Cons :: ∀a :: ∗.∀n :: Nat.a → Vec a n → Vec a (Succ n)
vecElim :: ∀a :: ∗.∀m :: (∀n :: Nat.Vec a n → ∗).
m Zero (Nil a)
→ (∀n :: Nat.∀x :: a.∀xs :: Vec a n.
m n xs → m (Succ n) (Cons a n x xs))
→ ∀n :: Nat.∀xs :: Vec a n.m n xs
Andres Löh
Implementing Dependent Types in Haskell
27
Vector append
append =
(λa → vecElim a
(λm → ∀(n :: Nat).Vec a n → Vec a (plus m n))
(λ v → v)
(λm v vs rec n w → Cons a (plus m n) v (rec n w)))
:: ∀(a :: ∗) (m :: Nat) (v :: Vec a m) (n :: Nat) (w :: Vec a n).
Vec a (plus m n)
Andres Löh
Implementing Dependent Types in Haskell
28
Other interesting types
Zero
One (or Unit)
Two (or Bool)
Fin
Eq
Holds
dependent pairs
...
Andres Löh
Implementing Dependent Types in Haskell
29
Lots of missing features
implicit arguments
proper case analysis
user feedback (error messages)
...
Andres Löh
Implementing Dependent Types in Haskell
30