220 likes | 334 Views
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
E N D
Implementing a Dependently Typed λ-Calculus Ali Assaf AbbieDesrosiers 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 Nwhere 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 datatype exp = var of int | lam of exp | app of exp * exp fun shift (var x) l b = if x > b then var (x + l) else var 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) fun sub (var y) s x l = 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)) HOAS in SML datatype exp = lam of (exp -> exp) | app of exp * exp; fun eval (lam m) = lam (fn x => eval (m x)) | eval (app (m, n)) = case eval m of lam m' => eval (m' n) | m' => app (m', n)
Our implementations Twelf exp : type. lam : (exp -> exp) -> exp. app : exp -> exp -> exp. 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'. SML datatype exp = lam of (exp -> exp) | app of exp * exp; fun eval (lam m) = lam (fn x => eval (m x)) | eval (app (m, n)) = case eval m of lam m' => eval (m' n) | m' => app (m', n)
The ω problem • Consider the function: ω = λ x . ( x x ) 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. • This corresponds to Curry style. 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.
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 : • Then ill-typed terms cannot even be constructed ! • This corresponds to Church style. 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.
Limitations of simple types • Decidable, but no longer Turing complete. • id = λ x : a . xid = λ x : b . xid = λ x : nat . xid = λ 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 = Lam u. (u) K = Lam w. (Lam v. (w)) O = Lam y. ((y)(y))
Conclusions and future work • Many type systems. • Various implementations. • Besides implementing, our goals include: • Proving equivalence of representations.