Towards Practical Gradual Typing

Towards Practical Gradual Typing

Slides from the ECOOP 2015 talk on "Towards Practical Gradual Typing". Licensed under CC-BY SA 4.0, except for the last slide.

Paper: http://drops.dagstuhl.de/opus/volltexte/2015/5215/pdf/5.pdf

7419acf39a7efd396dfea61c718022c7?s=128

Asumu Takikawa

July 08, 2015
Tweet

Transcript

  1. 1.

    Towards Practical Gradual Typing Asumu Takikawa Daniel Feltey Earl Dean

    Matthew Flatt Robert Bruce Findler Sam Tobin-Hochstadt Matthias Felleisen ECOOP 2015, Prague
  2. 2.

    Gradual type systems ... “empower programmers so that they can

    gradually enrich scripts with types [...] as they perform maintenance work on the program. [Felleisen TLDI 2010] ”
  3. 3.

    PL Researcher Hat Soundness enables reliable checked documentation, optimization λ

    Maintenance Programmer Hat For real software need expressiveness and performance
  4. 4.
  5. 9.

    Formal model [Takikawa et al. OOPSLA 2012] Implementation [Takikawa et

    al. ECOOP 2015] Our Story of Model → Impl. for Typed Racket & OOP
  6. 10.

    Formal model [Takikawa et al. OOPSLA 2012] Sound Implementation [Takikawa

    et al. ECOOP 2015] Expressive Performant Our Story of Model → Impl. for Typed Racket & OOP
  7. 11.

    Our three key contributions Propose a performance evaluation method for

    GT Expressiveness evaluation for our system Design lessons for OO gradual typing
  8. 13.

    Typed Racket implements a macro-level gradual type system which means

    we add types module-by-module = a module (untyped) = a program (4 modules)
  9. 14.

    Typed Racket implements a macro-level gradual type system which means

    we add types module-by-module #lang racket (define c% (class ....)) untyped
  10. 15.

    Typed Racket implements a macro-level gradual type system which means

    we add types module-by-module #lang typed/racket (: c% (Class [m (-> Int)])) (define c% (class ....)) typed
  11. 16.

    Modules agree to an interface at the boundary In Typed

    Racket it's expressed with types But interfaces also need to be enforced
  12. 18.
  13. 19.

    (object/c [m (-> integer? string?)]) I expect a (Object [m

    (-> Integer String)]) Sound interaction requires compilation of types to contracts at module boundaries
  14. 20.

    Higher-order contract requires a dynamic wrapper integer? string? Color on

    pipe corresponds to who is responsible for interface Method's input - typed Method's output - untyped
  15. 22.

    ; Implements editing window in DrRacket (text:line-numbers-mixin (text:first-line-mixin (module-language-put-file-mixin (racket:text-mixin

    (color:text-mixin (drracket:bindings-keymap-mixin (mode:host-text-mixin (text:delegate-mixin (text:foreground-color-mixin (drracket:autocomplete-mixin (λ (x) x) (text:normalize-paste-mixin (text:column-guide-mixin text:info%)))))))))))) OOP in Racket uses mixins and �rst-class classes Mixins are functions on classes
  16. 23.

    ; Implements editing window in DrRacket (text:line-numbers-mixin (text:first-line-mixin (module-language-put-file-mixin (racket:text-mixin

    (color:text-mixin (drracket:bindings-keymap-mixin (mode:host-text-mixin (text:delegate-mixin (text:foreground-color-mixin (drracket:autocomplete-mixin (λ (x) x) (text:normalize-paste-mixin (text:column-guide-mixin text:info%)))))))))))) OOP in Racket uses mixins and �rst-class classes Mixins are functions on classes
  17. 24.

    Mixins require structural subtyping and separate class types So in

    Typed Racket, types for classes/objects look like: (Class (init [enabled? Boolean]) (field [width Integer]) [get-width (-> Integer Integer)] (augment [get-width (-> Integer)])) (Object (field [width Integer]) [get-width (-> Integer Integer)]) Types are not just the names of classes
  18. 25.

    Background summary Typed Racket adds types module-by-module Dynamic checks, sometimes

    higher-order, added at boundaries Racket-style OOP requires structural types
  19. 28.

    How expressive is it? How much work is it to

    add type annotations? Does it support type reasoning in real programs? We tested it out by adding types to real untyped programs (e.g., GUI programs)
  20. 29.

    Performant - how fast is it? Credit: "White shark" by

    Terry Goss. Licensed under CC BY 2.5
  21. 30.

    How fast slow is it? Credit: "Manatee at Sea World

    Orlando Mar 10" by Ahodges7. Licensed under CC BY-SA 3.0.
  22. 31.

    GT suffers from cost of dynamic checks How slow does

    it get? In what situations are dynamic checks expensive?
  23. 32.

    GT suffers from cost of dynamic checks How slow does

    it get? In what situations are dynamic checks expensive?
  24. 33.

    GT suffers from cost of dynamic checks How slow does

    it get? In what situations are dynamic checks expensive?
  25. 34.

    Iterations through design/eval loop revealed several lessons Next section describes

    some of the lessons Evaluation Design feature Problem in adding types See if new features helps
  26. 39.

    Both self-recursion and mutual recursion Classes can have complex interfaces

    Often mutually recursive and have lots of methods
  27. 40.

    Both self-recursion and mutual recursion We need type syntax for

    both kinds of recursion Classes can have complex interfaces Often mutually recursive and have lots of methods
  28. 41.

    Class types are separate from class de�nitions So types duplicate

    code due to structural typing + inheritance
  29. 42.

    Class types are separate from class de�nitions So types duplicate

    code due to structural typing + inheritance ≥ 76 GUI classes in the real hierarchy
  30. 43.

    To reduce burden of writing types, Typed Racket has type

    aliases (define-type Path-String (U String Path)) We need our type aliases to do even more though
  31. 44.

    Solution part 1 General mutually recursive type aliases ; self

    recursion (define-type Button% (Class (init [callback (-> (Instance Button%) ...)]))) ; mutual recursion (define-type Bitmap% (Class [make-dc (-> (Instance Bitmap-DC%))] ...)) (define-type Bitmap-DC% (Class [get-bitmap (-> (U (Instance Bitmap%) #f))] ...))
  32. 45.

    Don't ordinary μ-types work? Recursive type aliases are similar to

    μ-types Far more convenient, especially for mutual recursion ; Mutual recursion with μ-types, it's a pain (define-type (T1 Y1 Y2) (U Null (Pairof String Y2))) (define-type (T2 Y1 Y2) (Pairof String Y1)) (define-type Even (μ Y1 (T1 Y1 (μ Y2 (T2 Y1 Y2))))) (define-type Odd (μ Y2 (T2 (μ Y1 (T1 Y1 Y2)) Y2)))
  33. 46.

    Solution part 2 Allow types to re�ect inheritance structure (define-type

    Text-Field% (Class [get-value (-> String)] [set-value (-> String Void)])) ; #:implements copies over get-value, set-value (define-type Combo-Field% (Class #:implements Text-Field% [append (-> String Void)]))
  34. 48.

    Performs tight loop Method impl Method call Returns same object

    Each boundary crossing adds a contract wrapper Can easily imagine at least linear growth
  35. 49.

    Performs tight loop Method impl Method call Returns same object

    Each boundary crossing adds a contract wrapper Can easily imagine at least linear growth
  36. 50.

    Performs tight loop Method impl Method call Returns same object

    Each boundary crossing adds a contract wrapper Can easily imagine at least linear growth
  37. 51.

    Performs tight loop Method impl Method call Returns same object

    Each boundary crossing adds a contract wrapper Can easily imagine at least linear growth
  38. 52.

    Performs tight loop Method impl Method call Returns same object

    Each boundary crossing adds a contract wrapper Can easily imagine at least linear growth
  39. 53.

    Performs tight loop Method impl Method call Returns same object

    Each boundary crossing adds a contract wrapper Can easily imagine at least linear growth
  40. 54.

    Performs tight loop Method impl Method call Returns same object

    Each boundary crossing adds a contract wrapper Can easily imagine at least linear growth
  41. 55.

    Solution Optimize unnecessary higher-order wrapping for objects Check if contracts

    are “stronger” than another Avoid a wrapper if the existing wrapper is stronger Inspired by space-ef�cient gradual typing [Herman et al. HOSC 2010] [Siek, Wadler POPL 2010] Not a new problem in theory. We found it occurs in real code
  42. 56.

    Design lessons Need a �exible type notation supporting recursion &

    inheritance Space-ef�ciency is needed, especially for object contracts
  43. 58.

    Left prong: how expressive is it? Added types to 12

    Racket case studies ~5000 LOC + ~6000 LOC type defs All use OO features, some use mixins Mostly succeeded in accommodating OO patterns A thirteenth case failed (lacked bounded polymorphism)
  44. 59.

    Overall 15% increase for total lines of code Compared to

    7% for functional code [Tobin-Hochstadt diss. 2010]
  45. 60.

    Overall 15% increase for total lines of code Compared to

    7% for functional code [Tobin-Hochstadt diss. 2010] Bottom line: expressive enough, but do better on LOC
  46. 61.

    Right prong: how does it perform? How do we even

    evaluate that for GT? The state of the literature No comprehensive method so far Evaluation targeted at speci�c features or microbenchmarks
  47. 62.

    Insight Remember the gradual typing thesis from slide 1 empower

    programmers so that they can gradually enrich scripts with types [...] as they perform maintenance work on the program. Let's try to simulate this process on real programs Then see what the performance is like
  48. 66.

    Operative phrase: gradually enrich from untyped to typed Fully untyped

    Type curious Commi�ed to types Fully typed
  49. 68.

    A �rst attempt: looked at lattices of 2 programs Are

    there “good” paths through lattice? Good as in minimal overhead or quick recovery Is fully typed performance ok or bad? Dynamic checks may remain in typed Are there pathological cases?
  50. 69.

    A �rst attempt: looked at lattices of 2 programs Are

    there “good” paths through lattice? Good as in minimal overhead or quick recovery Is fully typed performance ok or bad? Dynamic checks may remain in typed Are there pathological cases? Hypothesis No order-of-magnitude slowdown along any path
  51. 70.

    Acquire benchmark 39% 30% 38% 40% 38% 29% 31% 6%

    33% 38% 40% 24% 5% 8% 35% 0% Percentage slowdown relative to untyped con�g Note bad typed performance (due to base libraries)
  52. 71.

    Go Fish benchmark 0% 1% 12% 32% 2% 12% 33%

    1% 1% 12% 33% 1% 12% 32% 1% 0% Many paths have ok overhead at some point
  53. 72.

    We de�nitely had pathologies! Initial Acquire benchmark had exponential slowdown

    2ⁿ wrappers for n crossings of typed/untyped boundary
  54. 73.

    We de�nitely had pathologies! Initial Acquire benchmark had exponential slowdown

    2ⁿ wrappers for n crossings of typed/untyped boundary Just trying to make the lattice helped �nd this
  55. 74.

    What do evaluations tell us about future work? Annotation burden

    still too high More design work on notation, explore nominal Space ef�cient contracts important Current implementation is limited, should generalize Dynamic overhead too high JITing, nominal types, etc.
  56. 76.

    The Takeaway Evaluating performance of gradual typing is crucial We

    propose looking at lattice of type con�gurations Discover good/bad paths, pathological performance issues Gradual type systems should have a design/eval feedback cycle Model → Implementation → Evaluation → Model → ... http://docs.racket-lang.org/ts-guide/
  57. 77.

    The Takeaway Evaluating performance of gradual typing is crucial We

    propose looking at lattice of type con�gurations Discover good/bad paths, pathological performance issues Gradual type systems should have a design/eval feedback cycle Model → Implementation → Evaluation → Model → ... Thank you / Děkuji http://docs.racket-lang.org/ts-guide/