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

The State of Ruby 3 Typing

The State of Ruby 3 Typing

RubyConf 2020.

1fab9d01b25e99522f3dfd01e3d4cb51?s=128

Soutaro Matsumoto

November 17, 2020
Tweet

Transcript

  1. The State of Ruby 3 Typing Soutaro Matsumoto
 @soutaro

  2. The State of Ruby 3 Typing • Ruby 3 will

    ship with a feature to support type checkers. • A type de f i nition language RBS and rbs gem. • RBS type de f i nitions of standard libraries. • Several type checkers are available for Ruby. • You choose the best type checker for your projects.
  3. Type Checkers for Ruby Steep (@soutaro) TypeProf (@mame) Sorbet (Stripe)

    The most widely used type checker for Ruby. A static type checker for Ruby written in Ruby. Infers types in Ruby programs.
 (Bundled with Ruby 3.) RDL (Je f f Foster) Research project from Tufts University.
  4. Static Type Checking conference = Conference.new("RubyConf 2020") talks = [...]

    conference.talks.push(*talks) conference.each_speaker do |speaker| puts "%s (%s)" % [speaker.name, speaker.email] end
  5. Static Type Checking conference = Conference.new("RubyConf 2020") talks = [...]

    conference.talks.push(*talks) conference.each_speaker do |speaker| puts "%s (%s)" % [speaker.name, speaker.email] end Does new method accept a String argument?
  6. Static Type Checking conference = Conference.new("RubyConf 2020") talks = [...]

    conference.talks.push(*talks) conference.each_speaker do |speaker| puts "%s (%s)" % [speaker.name, speaker.email] end Does new method accept a String argument? What is the type of the value of #talks?
  7. Static Type Checking conference = Conference.new("RubyConf 2020") talks = [...]

    conference.talks.push(*talks) conference.each_speaker do |speaker| puts "%s (%s)" % [speaker.name, speaker.email] end Does new method accept a String argument? What is the type of the value of #talks? What is the type of the speaker?
  8. What is RBS? • A language to de f i

    ne types for Ruby programs. • Classes, modules, methods, instance variables, mixins, ... • A library and assets to help type checker developments.
  9. # The conference object represents a conference. # # conf

    = Conference.new("RubyConf 2020") # conf.talks << talk1 # conf.talks << talk2 # class Conference # The name of the conference. attr_reader name: String # Talks of the conference. attr_reader talks: Array[Talk] def initialize: (String name) -> void # Yields all speakers of the conference. # Deduped, and no order guaranteed. def each_speaker: { (Speaker) -> void } -> void | () -> Enumerator[Speaker, void] end
  10. # The conference object represents a conference. # # conf

    = Conference.new("RubyConf 2020") # conf.talks << talk1 # conf.talks << talk2 # class Conference # The name of the conference. attr_reader name: String # Talks of the conference. attr_reader talks: Array[Talk] def initialize: (String name) -> void # Yields all speakers of the conference. # Deduped, and no order guaranteed. def each_speaker: { (Speaker) -> void } -> void | () -> Enumerator[Speaker, void] end conference = Conference.new("RubyConf 2020") talks = [...] conference.talks.push(*talks) conference.each_speaker do |speaker| puts "%s (%s)" % [speaker.name, speaker.email] end
  11. # The conference object represents a conference. # # conf

    = Conference.new("RubyConf 2020") # conf.talks << talk1 # conf.talks << talk2 # class Conference # The name of the conference. attr_reader name: String # Talks of the conference. attr_reader talks: Array[Talk] def initialize: (String name) -> void # Yields all speakers of the conference. # Deduped, and no order guaranteed. def each_speaker: { (Speaker) -> void } -> void | () -> Enumerator[Speaker, void] end conference = Conference.new("RubyConf 2020") talks = [...] conference.talks.push(*talks) conference.each_speaker do |speaker| puts "%s (%s)" % [speaker.name, speaker.email] end
  12. # The conference object represents a conference. # # conf

    = Conference.new("RubyConf 2020") # conf.talks << talk1 # conf.talks << talk2 # class Conference # The name of the conference. attr_reader name: String # Talks of the conference. attr_reader talks: Array[Talk] def initialize: (String name) -> void # Yields all speakers of the conference. # Deduped, and no order guaranteed. def each_speaker: { (Speaker) -> void } -> void | () -> Enumerator[Speaker, void] end conference = Conference.new("RubyConf 2020") talks = [...] conference.talks.push(*talks) conference.each_speaker do |speaker| puts "%s (%s)" % [speaker.name, speaker.email] end
  13. # The conference object represents a conference. # # conf

    = Conference.new("RubyConf 2020") # conf.talks << talk1 # conf.talks << talk2 # class Conference # The name of the conference. attr_reader name: String # Talks of the conference. attr_reader talks: Array[Talk] def initialize: (String name) -> void # Yields all speakers of the conference. # Deduped, and no order guaranteed. def each_speaker: { (Speaker) -> void } -> void | () -> Enumerator[Speaker, void] end conference = Conference.new("RubyConf 2020") talks = [...] conference.talks.push(*talks) conference.each_speaker do |speaker| puts "%s (%s)" % [speaker.name, speaker.email] end
  14. RBS Features Union types Generics Interface types untyped type String

    | Symbol Array[String | Integer] Integer? #== Integer | nil Array[Integer] Hash[String, Array[Integer]] def eval: (String) -> untyped interface _IntegerConvertible def to_int: () -> Integer end
  15. https://github.com/ruby/rbs

  16. RBS for Gems • The main targets of RBS are

    Stdlib types and Gem types. Written by Used by Stdlib types Ruby committers (community) All Ruby developers Gem types Gem authors, community Gem users App types The developer of the app The developer of the app
  17. Ruby Gem types in RBS Stdlib types in RBS App

    types App types App types Type checker A Type checker B Type checker C
  18. Ruby Gem types in RBS Stdlib types in RBS App

    types App types App types in RBS Type checker A Type checker B Type checker C Lib types
  19. Type Checkers and RBS Steep TypeProf Sorbet Uses RBI as

    it's native type declaration language. Will support RBS too. (Maybe only a subset.) Uses RBS as the primary type language. Reads library types from RBS and generates RBS.
  20. FAQ • Why it requires writing types in other f

    i les?
  21. Why in Different Files? • To avoid writing types in

    Ruby code. • Matz believes type annotations will be outdated in future and wants no types written in Ruby code. • To keep type checking optional in Ruby. • To support libraries written in C.
  22. Inline Type Annotations • Types in Ruby code is out

    of scope of RBS. • We know type checkers need inline type annotations. class Conference extend T::Sig sig do params(name: String).void end def initialize(name:) @name = name @speakers = T.let([], T::Array[Speaker]) class Conference # Method types are in RBS in Steep. def each_speaker(&block) # @type var speakers: Array[Speaker] speakers = [] talks.each do |talk| speakers << talk.speaker end Sorbet Steep
  23. RBS of Gems • Type checkers need type de f

    i nitions of libraries == We need RBS of gems! • Distributing RBS • Releasing gems with RBS • RBS of existing gems • Writing RBS
  24. Releasing Gems with RBS • Add sig directory and put

    RBS f i les there.
  25. RBS of Existing Gems • Write RBS for gems and

    publish it in https://github.com/ruby/gem_rbs • Save RBS f i les in gems/gem_name/version directory
  26. RBS of Existing Gems • Multiple versions supported • Copy

    the version directories to make new version • Optimistic version resolution • Incompatible with semantic versioning • Required version 3.2.1 will resolve to 4.2 if there is no better version available
  27. Writing RBS of Gems • You can generate RBS from

    Ruby code • Sord will support generating RBS from YARD docs $ rbs prototype rb lib/**/*.rb $ rbs prototype runtime -r goodcheck $ typeprof exe/goodcheck
  28. module Goodcheck class ImportLoader attr_reader cache_path: untyped attr_reader expires_in: untyped

    def initialize: (cache_path: untyped cache_path, force_download: untyped force_download, config_path: untyped config_path, ?expires_in: untyped expires_in) -> untyped def load: (untyped name) { () -> untyped } -> untyped def load_file: (untyped path) { (untyped) -> untyped } -> untyped end end $ rbs prototype rb lib/**/*.rb
  29. module Goodcheck class ImportLoader attr_reader cache_path : Pathname attr_reader expires_in

    : Integer def initialize : (cache_path: Pathname, force_download: bool, config_path: Pathname, ?expires_in: Integer) -> Pathname def load : (untyped) ?{ (String) -> Array[untyped] } -> (Array[String] | Integer) def load_file : (untyped) ?{ (String) -> Array[untyped] } -> Array[String] end end $ typeprof exe/goodcheck
  30. The State of Ruby 3 Typing • Ruby 3 will

    ship with a feature to support type checkers. • A type de f i nition language RBS and rbs gem. • RBS type de f i nitions of standard libraries. • Several type checkers are available for Ruby. • You choose the best type checker for your projects. • https://github.com/ruby/gem_rbs
  31. @soutaro