* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Download View raw file - aaa
Survey
Document related concepts
Transcript
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.