An introduction to typed Ruby programming

An introduction to typed Ruby programming

Balkan Ruby 2019, Sofia, Bulgaria.

1fab9d01b25e99522f3dfd01e3d4cb51?s=128

Soutaro Matsumoto

May 18, 2019
Tweet

Transcript

  1. An introduction to typed Ruby programming Soutaro Matsumoto @soutaro

  2. Soutaro Matsumoto

  3. Soutaro Matsumoto From Tokyo, Japan

  4. Soutaro Matsumoto From Tokyo, Japan

  5. Soutaro Matsumoto From Tokyo, Japan Working as CTO of Sider

  6. Soutaro Matsumoto From Tokyo, Japan Working as CTO of Sider

  7. Soutaro Matsumoto From Tokyo, Japan Working as CTO of Sider

  8. Soutaro Matsumoto From Tokyo, Japan Develops a type checker for

    Ruby Working as CTO of Sider
  9. Outline • The plan for Ruby3 ← • An introduction

    to Steep, my static type checker for Ruby • The progress
  10. None
  11. The team From Ruby team Type checker developers Matz Koichi

    Sasada Yusuke Endoh Dmitry Petrashko and Sorbet team (Sorbet) Jeff Foster (RDL) Yusuke Endoh (type-profiler) Soutaro Matsumoto (Steep) Have meetings to share the progress and develop the foundation of type checking for Ruby
  12. The goal • Detecting types of each Ruby expression statically

    • To help development of Ruby programs • Finding bugs (NoMethodErrors) • Reading code: jump to definition • Refactoring: renaming constants, methods, ... • Performance is not our goal (it needs much more precise analysis)
  13. Ruby 3 will have four key items Sorbet Steep RDL

    Library code type signature Type error warnings Application code type signature type signature Type Profiler Type error warnings 3. Type signature profiling 2. Level-1 checking 4. Level-2 checking 1. Type signature format
  14. The plan (rephrased) • Ruby3 will provide: • A language

    to define signatures of Ruby programs • Signature of standard library • A protocol to share the signatures of gems
  15. The Ruby signature language • Ruby-like but different syntax •

    Defines the structure of Ruby programs • Classes, modules, interfaces, and mixin • Methods and instance variables • Generics, unions, tuples, optionals, ... • Covers most of the standard libraries class Array[A] include Enumerable def []=: (Integer, A) -> A def []: (Integer) -> A? def each: { (A) -> void } -> self def partition: { (A) -> bool } -> [Array[A], Array[A]] ... end WIP
  16. Stdlib and gem signatures • To type check your application,

    you need the signatures of libraries • Standard library and gems • You don't want to write yourself • Ruby3 will ship with standard library signatures • Ruby3 will define the standard protocol to share signature of gems
  17. The plan (rephrased) • Ruby3 won't provide: • New syntax

    to annotate Ruby code • The standard type checker
  18. No syntax for type annotation def inject(start): [A] (A) {

    (A, X) -> A } -> A ... end New type annotation syntax (rejected in Ruby3)
  19. No syntax for type annotation def inject(start): [A] (A) {

    (A, X) -> A } -> A ... end New type annotation syntax (rejected in Ruby3) # @type method inject: [A] (A) { (A, Elm) -> A } -> A def inject(start) end Type annotation using comment (Steep)
  20. No syntax for type annotation def inject(start): [A] (A) {

    (A, X) -> A } -> A ... end New type annotation syntax (rejected in Ruby3) # @type method inject: [A] (A) { (A, Elm) -> A } -> A def inject(start) end Type annotation using comment (Steep) sig { type_parameters(:U). .params(start: T.type_parameter(:U), 
 blk: T.proc.params(arg0: Elem, T.type_parameter(:A)).returns(T.type_parameter(:U))) .returns(T.type_parameter(:A)) } def inject(start, &blk) end Type annotation using embedded DSL (Sorbet)
  21. No type checkers • We develop type checkers, but the

    tools are not a part of Ruby3 • No tool works as Matz expected... • Steep requires too much annotations • Sorbet's modeling of duck typing requires extra code • type-profiler is still too experimental
  22. The plan (rephrased) • Ruby3 will provides: • A language

    to define signatures of Ruby programs • Signature of standard library • A protocol to share the signatures of gems • Ruby3 won't provide: • New syntax to annotate Ruby code • The standard type checker
  23. Typing experience in Ruby3 • Choose the best level for

    your project • My recommendation is level 2 Library Application Type checking Tools Level 0 No No No --- Level 1 Use library signatures No signature Weaker type-profiler Level 2 Use library signatures You write signature Stronger Sorbet, Steep
  24. Outline • The plan for Ruby3 • An introduction to

    Steep, my static type checker for Ruby ← • The progress
  25. Steep key ideas • Structural subtyping (for duck typing) •

    Metaprogramming agnostic • Static type checker without runtime checks/library $ gem install steep https://github.com/soutaro/steep
  26. class Conference attr_reader :name attr_reader :talks def initialize(name:) @name =

    name @talks = [] end def speakers talks.each(&:speaker) end end conference = Conference.new("Balkan Ruby 2019") conference.talks << Talk.new(...)
  27. class Conference attr_reader name: String attr_reader talks: Array<Talk> def initialize:

    (name: String) -> void def speakers: -> Array<Speaker> end class Talk ... end class Speaker ... end class Conference attr_reader :name attr_reader :talks def initialize(name:) @name = name @talks = [] end def speakers talks.each(&:speaker) end end conference = Conference.new("BalkanRuby 2019") conference.talks << Talk.new(...)
  28. class Conference attr_reader :name attr_reader :talks def initialize(name:) @name =

    name @talks = [] end def speakers talks.each(&:speaker) end end conference = Conference.new("Balkan Ruby 2019") conference.talks << Talk.new(...) class Conference attr_reader name: String attr_reader talks: Array<Talk> def initialize: (name: String) -> void def speakers: -> Array<Speaker> end class Talk ... end class Speaker ... end ArgumentTypeMismatch: receiver=::Conference.class constructor, expected={ :name => ::String }, actual=::String
  29. class Conference attr_reader :name attr_reader :talks def initialize(name:) @name =

    name @talks = [] end def speakers talks.each(&:speaker) end end conference = Conference.new("Balkan Ruby 2019") conference.talks << Talk.new(...) class Conference attr_reader name: String attr_reader talks: Array<Talk> def initialize: (name: String) -> void def speakers: -> Array<Speaker> end class Talk ... end class Speaker ... end MethodBodyTypeMismatch: method=speakers, expected=::Array<::Speaker>, actual=::Array<::Talk>
  30. None
  31. None
  32. rubber duck

  33. Duck typing support interface _DumpTo def <<: (String) -> any

    end class Conference ... def dump_titles: (_DumpTo) -> void end # conference.dump_titles("") # conference.dump_titles([]) # Error conference.dump_titles(3)
  34. Metaprogramming agnostic • Steep does nothing about metaprogramming • It

    detects type errors on metaprogramming method calls • It doesn't know the result of metaprogramming • You can use any metaprogramming as you like class Person # Error: attr_reader doesn't accept class attr_reader Name end class Class def attr_reader: (*Symbol|String) -> ... end class Person @name: Name def name: -> Name end
  35. No runtime library • Inline type annotations of Steep are

    comments • Embedded DSL requires extra runtime dependency • Better for libraries: • Keep # of runtime dependencies as small as possible • Your library users would not want to install Steep spec.add_development_dependency "steep"
  36. Outline • The plan for Ruby3 • An introduction to

    Steep, my static type checker for Ruby • The progress ←
  37. The progress • Type checkers • Development in progress •

    Type definition language • Develop a ruby library to process type definitions • https://github.com/ruby/ruby-signature • Library types • Writing signature of standard libraries • Starting a discussion for gem types
  38. My proposal for gem sigs (1) • Ship your gems

    with signature files • Include signature files in the .gem • Declare in metadata that the gem has signatures Gem::Specification.new do |spec| spec.name = "rakia" spec.files = ["lib/rakia.rb", "sig/rakia.rbi"] spec.metadata = { "signature_dir" => "sig" } end
  39. My proposal for gem sigs (2) • Let community write

    gem signatures for the case the authors don't provide signatures (DefinitelyTyped in TypeScript) source "https://rubygems.org" gem "rails", version: "~> 6.0.0" gem "type--rails"
  40. Get ready for types! • Give us feedbacks • Test

    type checkers with your programs • $ gem install steep • Sign up Sorbet beta program • Try writing types for your gems/apps • Send a patch to add standard library signatures • https://github.com/ruby/ruby-signature
  41. References • Projects • https://github.com/soutaro/steep • https://sorbet.org • https://github.com/plum-umd/rdl •

    https://github.com/mame/ruby-type-profiler • https://github.com/ruby/ruby-signature • Sider • https://sider.review • https://blog.sideci.com/interview-with-bozhidar-batsov-99b049b6fd6a