CSE 505: Programming Languages Lecture 17 — Subtyping Zach Tatlock Fall 2013
Tradeoffs Desirable type system properties ( desiderata ): ◮ soundness - exclude all programs that get stuck ◮ completeness - include all programs that don’t get stuck ◮ decidability - effectively determine if a program has a type Our friend Turing says we can’t have it all. We choose soundness and decidability, aim for “reasonable” completeness, but still reject valid programs. Any benefit to an unsound , complete, decidable type system? Today: subtype polymorphism to start adding completeness. Next Lecture: parametric polymorphism to get even more. Zach Tatlock CSE 505 Fall 2013, Lecture 17 2
Where shall we add completeness? if true 1 (2 , 3) does not get stuck, but we can’t type it either. Perhaps we should add this typing rule? ∗ e 1 − → true Γ ⊢ e 2 : τ Γ ⊢ if e 1 e 2 e 3 : τ Not if we want to keep decidability! How about? Γ ⊢ e 2 : τ Γ ⊢ if true e 2 e 3 : τ Sound, adds completeness, but not terribly useful. Zach Tatlock CSE 505 Fall 2013, Lecture 17 3
Where shall we add useful completeness? Code reuse is crucial: write code once, use it in many contexts. Polymorphism supports code reuse and comes in several flavors: ◮ ad hoc - implementation depends on type details + in ML vs. C vs. C++ ◮ parametric - implementation independent of type details Γ ⊢ λx. x : ∀ α.α → α ◮ subtype - implementation assumes constrained types void makeSound(Dog d) { ....d.growl(); } ... makeSound(new Husky()); Subtyping uses a value of type τ as a different type τ ′ . Zach Tatlock CSE 505 Fall 2013, Lecture 17 4
Where shall we add useful completeness? Code reuse is crucial: write code once, use it in many contexts. Polymorphism supports code reuse and comes in several flavors: ◮ ad hoc - implementation depends on type details + in ML vs. C vs. C++ ◮ parametric - implementation independent of type details Γ ⊢ λx. x : ∀ α → α ◮ subtype - implementation assumes constrained types void makeSound(Dog d) { ....d.growl(); } ... makeSound(new Husky()); Subtyping uses a value of type A as a different type B . Zach Tatlock CSE 505 Fall 2013, Lecture 17 5
Where shall we add useful completeness? Subtyping. Wait... how many types can a STLC expression have? At most one! Currently we have no polymorphism :( If Γ ⊢ e : τ 1 and Γ ⊢ e : τ 2 , then τ 1 = τ 2 Let’s fix that: ◮ add completeness by extending STLC with subtyping ◮ consider implications for the compiler ◮ also touch on coercions and downcasts Guiding principle: If A is a subtype of B (written A ≤ B ), then we can safely use a value of type A anywhere a value of type B is expected. Zach Tatlock CSE 505 Fall 2013, Lecture 17 6
Extending STLC with Subtyping We know the extension recipe: 1. add new syntax 2. add new semantic rules 3. add new typing rules 4. update type safety proof Zach Tatlock CSE 505 Fall 2013, Lecture 17 7
Extending STLC with Subtyping We know the extension recipe: already half done! 1. add new syntax 2. add new semantic rules 3. add new typing rules 4. update type safety proof Where to start adding new typing rules? First, let’s focus on records : ◮ review existing rules ◮ consider examples of incompleteness ◮ add new rules to handle examples and improve completeness Zach Tatlock CSE 505 Fall 2013, Lecture 17 8
Records Review e ::= . . . | { l 1 = e 1 , . . . , l n = e n } | e.l ::= . . . | { l 1 : τ 1 , . . . , l n : τ n } τ v ::= . . . | { l 1 = v 1 , . . . , l n = v n } { l 1 = v 1 , . . . , l n = v n } .l i → v i e i → e ′ e → e ′ i { l 1 = v 1 , . . . , l i − 1 = v i − 1 , l i = e i , . . . , l n = e n } e.l → e.l → { l 1 = v 1 , . . . , l i − 1 = v i − 1 , l i = e ′ i , . . . , l n = e n } Γ ⊢ e 1 : τ 1 . . . Γ ⊢ e n : τ n labels distinct Γ ⊢ { l 1 = e 1 , . . . , l n = e n } : { l 1 : τ 1 , . . . , l n : τ n } Γ ⊢ e : { l 1 : τ 1 , . . . , l n : τ n } 1 ≤ i ≤ n Γ ⊢ e.l i : τ i Zach Tatlock CSE 505 Fall 2013, Lecture 17 9
Should this typecheck? ( λx : { l 1 : int , l 2 : int } . x.l 1 + x.l 2 ) { l 1 =3 , l 2 =4 , l 3 =5 } Sure! It won’t get stuck. Suggests width subtyping : τ 1 ≤ τ 2 { l 1 : τ 1 , . . . , l n : τ n , l : τ } ≤ { l 1 : τ 1 , . . . , l n : τ n } Add new typing rule to take advantage of subtyping: Subsumption subsumption τ ′ ≤ τ Γ ⊢ e : τ ′ Γ ⊢ e : τ Zach Tatlock CSE 505 Fall 2013, Lecture 17 10
Now it type-checks . · ⊢ 3 : int · ⊢ 4 : int · ⊢ 5 : int . . · ⊢ { l 1 =3 , l 2 =4 , l 3 =5 } : { l 1 : int , l 2 : int , l 3 : int } · , x : { l 1 : int , l 2 : int } ⊢ x.l 1 + x.l 2 : int { l 1 : int , l 2 : int , l 3 : int } ≤ { l 1 : int , l 2 : int } · ⊢ λx : { l 1 : int , l 2 : int } . x.l 1 + x.l 2 : { l 1 : int , l 2 : int } → int · ⊢ { l 1 =3 , l 2 =4 , l 3 =5 } : { l 1 : int , l 2 : int } · ⊢ ( λx : { l 1 : int , l 2 : int } . x.l 1 + x.l 2 ) { l 1 =3 , l 2 =4 , l 3 =5 } : int Instantiation of Subsumption is highlighted (pardon formatting) The derivation of the subtyping fact { l 1 : int , l 2 : int , l 3 : int } ≤ { l 1 : int , l 2 : int } would continue, using rules for the τ 1 ≤ τ 2 . So far we only have one subtyping axiom, just use that. Clean division of responsibility: ◮ Where to use subsumption ◮ How to show two types are subtypes Zach Tatlock CSE 505 Fall 2013, Lecture 17 11
Permutation Does this program type-check? Does it get stuck? ( λx : { l 1 : int , l 2 : int } . x.l 1 + x.l 2 ) { l 2 =3; l 1 =4 } Suggests permutation subtyping : { l 1 : τ 1 , . . . , l i − 1 : τ i − 1 , l i : τ i , . . . , l n : τ n } ≤ { l 1 : τ 1 , . . . , l i : τ i , l i − 1 : τ i − 1 , . . . , l n : τ n } Example with width and permutation. Show: · ⊢ { l 1 =7 , l 2 =8 , l 3 =9 } : { l 2 : int , l 1 : int } No longer obvious, efficient, sound, complete type-checking algo: ◮ sometimes such algorithms exist and sometimes they don’t ◮ in this case, we have them Zach Tatlock CSE 505 Fall 2013, Lecture 17 12
Reflexive Transitive Closure The subtyping principle implies reflexivity and transtivity: τ 1 ≤ τ 2 τ 2 ≤ τ 3 τ ≤ τ τ 1 ≤ τ 3 Could get transitivity w/ multiple subsumptions anyway. Have we lost anything while gaining all these rules? Type-checking no longer syntax-directed : ◮ may be 0, 1, or many distinct derivations of Γ ⊢ e : τ ◮ many potential ways to show τ 1 ≤ τ 2 Still decidable? Need algorithm checking that labels always a subset of what’s required, must prove it “answers yes” iff there exists a derivation. Still efficient? Zach Tatlock CSE 505 Fall 2013, Lecture 17 13
Implementation Efficiency Given semantics, width and permutation subtyping totally reasonable. How do they impact the lives of our dear friend, the compiler writer? It would be nice to compile e.l down to: 1. evaluate e to a record stored at an address a 2. load a into a register r 1 3. load field l from a fixed offset (e.g., 4) into r 2 Many type systems are engineered to make this easy for compiler writers. In general: If some language restriction seems odd, ask yourself: what useful invariant does limiting expressiveness provide the compiler? Zach Tatlock CSE 505 Fall 2013, Lecture 17 14
Implementation Efficiency Changes to implement width subtyping alone? None. Changes to implement permutation subtyping alone? Sort fields . Changes to implement both? Not so easy. . . f 1 : { l 1 : int } → int f 2 : { l 2 : int } → int x 1 = { l 1 = 0 , l 2 = 0 } x 2 = { l 2 = 0 , l 3 = 0 } f 1 ( x 1 ) f 2 ( x 1 ) f 2 ( x 2 ) Can use dictionary-passing to look up offset at run-time and maybe optimize away some lookups. Zach Tatlock CSE 505 Fall 2013, Lecture 17 15
Getting some sweet completeness. Added new subtyping judgement : ◮ width, permutation, reflexive transitive closure { l 1 : τ 1 , . . . , l n : τ n , l : τ } ≤ { l 1 : τ 1 , . . . , l n : τ n } τ ≤ τ τ 1 ≤ τ 2 τ 2 ≤ τ 3 { l 1 : τ 1 , . . . , l i − 1 : τ i − 1 , l i : τ i , . . . , l n : τ n } ≤ τ 1 ≤ τ 3 { l 1 : τ 1 , . . . , l i : τ i , l i − 1 : τ i − 1 , . . . , l n : τ n } Added new typing rule, subsumption , to use subtyping: τ ′ ≤ τ Γ ⊢ e : τ ′ Γ ⊢ e : τ Squeeze out more completeness: ◮ Extend subtyping to “parts” of larger types ◮ Example: Can’t yet use subsumption on a record field’s type ◮ Example: Don’t yet have supertypes of τ 1 → τ 2 Zach Tatlock CSE 505 Fall 2013, Lecture 17 16
Depth Does this program type-check? Does it get stuck? ( λx : { l 1 : { l 3 : int } , l 2 : int } . x.l 1 .l 3 + x.l 2 ) { l 1 = { l 3 =3 , l 4 =9 } , l 2 =4 } Suggests depth subtyping τ i ≤ τ ′ i { l 1 : τ 1 , . . . , l i : τ i , . . . , l n : τ n } ≤ { l 1 : τ 1 , . . . , l i : τ ′ i , . . . , l n : τ n } (With permutation subtyping, can just have depth on left-most field) Zach Tatlock CSE 505 Fall 2013, Lecture 17 17
Recommend
More recommend