Upgrade to Pro — share decks privately, control downloads, hide ads and more …

An introduction to typed Ruby programming

An introduction to typed Ruby programming

Balkan Ruby 2019, Sofia, Bulgaria.

Soutaro Matsumoto

May 18, 2019
Tweet

More Decks by Soutaro Matsumoto

Other Decks in Programming

Transcript

  1. An introduction to
    typed Ruby programming
    Soutaro Matsumoto

    @soutaro

    View full-size slide

  2. Soutaro Matsumoto

    View full-size slide

  3. Soutaro Matsumoto
    From Tokyo, Japan

    View full-size slide

  4. Soutaro Matsumoto
    From Tokyo, Japan

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  8. Soutaro Matsumoto
    From Tokyo, Japan
    Develops a type checker for Ruby
    Working as CTO of Sider

    View full-size slide

  9. Outline
    • The plan for Ruby3 ←

    • An introduction to Steep, my static type checker for Ruby

    • The progress

    View full-size slide

  10. 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

    View full-size slide

  11. 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)

    View full-size slide

  12. 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

    View full-size slide

  13. 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

    View full-size slide

  14. 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

    View full-size slide

  15. 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

    View full-size slide

  16. The plan (rephrased)
    • Ruby3 won't provide:

    • New syntax to annotate Ruby code

    • The standard type checker

    View full-size slide

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

    View full-size slide

  18. 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)

    View full-size slide

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

    View full-size slide

  20. 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

    View full-size slide

  21. 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

    View full-size slide

  22. 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

    View full-size slide

  23. Outline
    • The plan for Ruby3

    • An introduction to Steep, my static type checker for Ruby ←

    • The progress

    View full-size slide

  24. 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

    View full-size slide

  25. 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(...)

    View full-size slide

  26. class Conference
    attr_reader name: String
    attr_reader talks: Array
    def initialize: (name: String) -> void
    def speakers: -> Array
    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(...)

    View full-size slide

  27. 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
    def initialize: (name: String) -> void
    def speakers: -> Array
    end
    class Talk ... end
    class Speaker ... end
    ArgumentTypeMismatch: receiver=::Conference.class constructor,
    expected={ :name => ::String },
    actual=::String

    View full-size slide

  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
    def initialize: (name: String) -> void
    def speakers: -> Array
    end
    class Talk ... end
    class Speaker ... end
    MethodBodyTypeMismatch: method=speakers,
    expected=::Array<::Speaker>,
    actual=::Array<::Talk>

    View full-size slide

  29. 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)

    View full-size slide

  30. 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

    View full-size slide

  31. 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"

    View full-size slide

  32. Outline
    • The plan for Ruby3

    • An introduction to Steep, my static type checker for Ruby

    • The progress ←

    View full-size slide

  33. 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

    View full-size slide

  34. 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

    View full-size slide

  35. 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"

    View full-size slide

  36. 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

    View full-size slide

  37. 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

    View full-size slide