View raw file - aaa
Download
Report
Transcript View raw file - aaa
Implementing a Dependently
Typed λ-Calculus
Ali Assaf
Abbie Desrosiers
Alexandre Tomberg
Outline
λ-calculus.
Implementation strategies
Typed λ-calculus
Polymorphic types, System F
Dependent types
Current work
Motivations
λ-calculus expresses function abstractions
◦ Powerful, Turing-complete.
◦ Foundation of functional programming
languages.
◦ Strong connection with logic.
This project:
◦ Study types
◦ Explore different implementations
The λ-calculus
Very simple language:
◦ Terms M = x | λ x . M | M N
◦ Reductions (λ x . M) N => [N/x] M
Examples
◦ Identity: λ x . x
◦ Constant function: λ x . λ y . x
◦ Omega: λ x . (x x)
Implementation strategies
Explicit substitution
◦ term = var “x” | lam “x” M | app M N
◦ “Natural way” of representing lambda
expressions.
◦ Problems :
No immediate α-renaming
Free variable capture problem
Implementation strategies (cont’d)
De Bruijn indices
◦ term M = Var n | lam M | app M N
◦ e.g. Identity: λ . 1, Constant: λ . λ . 2
◦ Advantages:
Unique representation
◦ Problems:
Not simple to manipulate.
λ x . (λ y . x) x = λ . (λ . 2). 1
Need to shift indices in substitution to avoid
capture.
Implementation strategies (cont’d)
Higher Order Abstract Syntax (HOAS)
◦ Use the features of the meta-language !
◦ term M = lam f | app M N
where f is a function term → term
◦ Term substitution :
sub (lam f) N => lam (f N)
◦ α-renaming already provided!
◦ WARNING : Leads to ridiculously short
implementations.
Our implementations
De Bruijn in SML
HOAS in SML
datatype exp = var of int | lam of exp | app of exp * exp
datatype exp = lam of (exp -> exp) | app of exp * exp;
fun shift (var x) l b = if x > b then var (x + l) else var x
fun eval (lam m) = lam (fn x => eval (m x))
| shift (lam m) l b = lam (shift m l (b + 1))
| shift (app (m, n)) l b = app(shift m l b, shift n l b)
| eval (app (m, n)) =
case eval m of
lam m' => eval (m' n)
fun sub (var y) s x l =
| m' => app (m', n)
if y = x then shift s l 0
(* Substitution *)
else if y > x then var (y-1)
(* Free variable *)
else var y
(* Bound variable *)
| sub (lam m) s x l = lam (sub m s (x + 1) (l + 1))
| sub (app (m, n)) s x l = app (sub m s x l, sub n s x l)
fun eval (var x) = var x
| eval (lam m) = lam (eval m)
| eval (app (m, n)) = case eval m of
lam m' => eval (sub m' n 1 0)
| m' => (app (m', eval n))
Our implementations
Twelf
exp : type.
lam : (exp -> exp) -> exp.
SML
datatype exp = lam of (exp -> exp)
| app of exp * exp;
app : exp -> exp -> exp.
fun eval (lam m) = lam (fn x => eval (m x))
eval : exp -> exp -> type.
eval/lam : eval (lam M) (lam M).
eval/app : eval (app M N) N'
<- eval M (lam M')
<- eval (M' N) N'.
| eval (app (m, n)) =
case eval m of
lam m' => eval (m' n)
| m' => app (m', n)
The ω problem
Consider the function:
ω=λx.(xx)
What happens if we apply it to itself?
ω ω = (λ x . ( x x )) ω => ω ω => ω ω => ...
Evaluation never terminates!
We say (ω ω) doesn’t have a normal form.
To solve this problem, we introduce
types.
Types
Each function f has a fixed domain A and
co-domain B (like sets in math).
types τ = a | τ → σ
where a is a base type (e.g. nat)
Typing rules
◦ XXXXXXXXXXXXXX
Return of the ω
What happens to ω ?
◦ ω = λ x. xx
ω:α→β
x : α, but also x : α → β
α≠α→β
ω cannot have a valid type!
Simply-typed lambda calculus is strongly
normalizing.
Implementing types
Much easier in Twelf: use HOAS.
t : type.
a : t.
arrow : t -> t -> t.
%infix right 10 arrow.
exp : type.
lam : (exp -> exp) -> exp.
app : exp -> exp -> exp.
check : exp -> t -> type.
check/lam : check (lam M) (A arrow B)
<- {x:exp} (check x A -> check (M x) B).
check/app : check (app M N) B
<- check M (A arrow B)
<- check N A.
This corresponds to Curry style.
Examples
Type checking:
◦ check (lam ([x] x)) (A arrow A)
Secret weapon: we can use Twelf to do
type inference !
◦ %query 1 * check (lam ([x] x)) T
T = X1 arrow X2 arrow X1.
ω?
◦ %query 1 * check (lam ([x] app x x)) T
Query error -- wrong number of solutions:
expected 1 in * tries, but found 0
Another implementation
Cute trick :
t : type.
a : t.
arrow : t -> t -> t.
%infix right 10 arrow.
exp : t -> type.
lam : (exp T1 -> exp T2) -> exp (T1 arrow T2).
app : exp (T1 arrow T2) -> exp T1 -> exp T2.
Then ill-typed terms cannot even be
constructed !
This corresponds to Church style.
Limitations of simple types
Decidable, but no longer Turing complete.
id = λ x : a . x
id = λ x : b . x
id = λ x : nat . x
id = λ x : (a → a) . x
We need to write a different identity
function for each type.
◦ To avoid that we need polymorphism.
Polymorphism: System F
Idea: Allow abstraction over types.
Then, we can write a single identity
function for any type a:
Λa.λx:a.x
We extend our definition of types and
terms:
◦ τ =a|τ→τ|Πa.τ
◦ M = x | λ x : τ . M | M N | Λ a . M | M {τ}
Implementations follow the same idea.
Dependent types : LF
Idea: Let types depend on values.
Keep information about our types.
Examples:
◦ Vector : Nat → type.
◦ Vector n is the type of vectors of length n.
◦ Exp t
Advantages:
◦ We won’t need to prove properties about
our programs !
Current work
Polymorphic types in Twelf
Dependent types in Twelf
Types in SML
◦ Problems with HOAS.
Problems with HOAS
Current functional languages (like SML)
present a few problems:
◦ Do not allow us to look inside functions.
◦ Hard to generate λ terms.
Solutions:
◦ Improve functional programming languages.
Beluga
◦ Try different paradigms.
Python
Why Python?
Dynamically typed.
Very flexible code generation.
User-friendly interface.
I = Lam(lambda x : x)
K = Lam(lambda x : Lam (lambda y : x))
O = Lam(lambda x : App(x, x))
print "I = ", I
print "K = ", K
print "O = ", O
>>>
I =
K =
O =
Lam u. (u)
Lam w. (Lam v. (w))
Lam y. ((y)(y))
Conclusions and future work
Many type systems.
Various implementations.
Besides implementing, our
goals include:
◦ Proving equivalence of
representations.