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. Towards Practical Gradual Typing Asumu Takikawa Daniel Feltey Earl Dean

    Matthew Flatt Robert Bruce Findler Sam Tobin-Hochstadt Matthias Felleisen ECOOP 2015, Prague
  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. PL Researcher Hat Soundness enables reliable checked documentation, optimization λ

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

  5. Sound Practical

  6. Sound Practical Expressive

  7. Sound Practical Expressive Performant

  8. Sound Practical Expressive Design + Eval Feedback Cycle Performant

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

    al. ECOOP 2015] Our Story of Model → Impl. for Typed Racket & OOP
  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
  11. Our three key contributions Propose a performance evaluation method for

    GT Expressiveness evaluation for our system Design lessons for OO gradual typing
  12. Background on Typed Racket

  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)
  14. Typed Racket implements a macro-level gradual type system which means

    we add types module-by-module #lang racket (define c% (class ....)) untyped
  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
  16. Modules agree to an interface at the boundary In Typed

    Racket it's expressed with types But interfaces also need to be enforced
  17. integer? I expect an Integer Sound interaction requires compilation of

    types to contracts at module boundaries
  18. string? I expect a String Sound interaction requires compilation of

    types to contracts at module boundaries
  19. (object/c [m (-> integer? string?)]) I expect a (Object [m

    (-> Integer String)]) Sound interaction requires compilation of types to contracts at module boundaries
  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
  21. OO in Typed Racket

  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
  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
  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
  25. Background summary Typed Racket adds types module-by-module Dynamic checks, sometimes

    higher-order, added at boundaries Racket-style OOP requires structural types
  26. Evaluation Criteria

  27. How expressive is it? How performant is it?

  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)
  29. Performant - how fast is it? Credit: "White shark" by

    Terry Goss. Licensed under CC BY 2.5
  30. How fast slow is it? Credit: "Manatee at Sea World

    Orlando Mar 10" by Ahodges7. Licensed under CC BY-SA 3.0.
  31. GT suffers from cost of dynamic checks How slow does

    it get? In what situations are dynamic checks expensive?
  32. GT suffers from cost of dynamic checks How slow does

    it get? In what situations are dynamic checks expensive?
  33. GT suffers from cost of dynamic checks How slow does

    it get? In what situations are dynamic checks expensive?
  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
  35. Design Lessons

  36. Design lesson = problem + solution The problems are condensed

    issues from real code bases
  37. Problem 1 Structural types for the GUI “standard library” are

    complex
  38. Classes can have complex interfaces Often mutually recursive and have

    lots of methods
  39. Both self-recursion and mutual recursion Classes can have complex interfaces

    Often mutually recursive and have lots of methods
  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
  41. Class types are separate from class de�nitions So types duplicate

    code due to structural typing + inheritance
  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
  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
  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))] ...))
  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)))
  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)]))
  47. Problem 2 High overhead of higher-order object contracts

  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
  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
  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
  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
  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
  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
  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
  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
  56. Design lessons Need a �exible type notation supporting recursion &

    inheritance Space-ef�ciency is needed, especially for object contracts
  57. Detailed Evaluation

  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)
  59. Overall 15% increase for total lines of code Compared to

    7% for functional code [Tobin-Hochstadt diss. 2010]
  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
  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
  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
  63. Operative phrase: gradually enrich from untyped to typed Fully untyped

  64. Operative phrase: gradually enrich from untyped to typed Fully untyped

    Type curious
  65. Operative phrase: gradually enrich from untyped to typed Fully untyped

    Type curious Commi�ed to types
  66. Operative phrase: gradually enrich from untyped to typed Fully untyped

    Type curious Commi�ed to types Fully typed
  67. Operative phrase: gradually enrich from untyped to typed Fully untyped

    Fully typed
  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?
  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
  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)
  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
  72. We de�nitely had pathologies! Initial Acquire benchmark had exponential slowdown

    2ⁿ wrappers for n crossings of typed/untyped boundary
  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
  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.
  75. Conclusion

  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/
  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/