Exploring Implementation Strategies of Dependent Typed λ-Calculus
220 likes | 348 Views
This project delves into the nuances of λ-calculus, focusing on implementation strategies used for typed and dependent types. It highlights the foundational aspects of λ-calculus, which express function abstractions and play a crucial role in functional programming languages. We'll study unique representation methods, contrasting traditional implementations like De Bruijn indices with higher-order abstract syntax (HOAS). The presentation aims to clarify typing methodologies, including how types can restrict term formation, ultimately enhancing our understanding of polymorphic types and System F.
Exploring Implementation Strategies of Dependent Typed λ-Calculus
E N D
Presentation Transcript
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.