Slide 1

Slide 1 text

The State of Ruby 3 Typing Soutaro Matsumoto
 @soutaro

Slide 2

Slide 2 text

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.

Slide 3

Slide 3 text

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.

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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?

Slide 6

Slide 6 text

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?

Slide 7

Slide 7 text

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?

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

# 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

Slide 10

Slide 10 text

# 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

Slide 11

Slide 11 text

# 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

Slide 12

Slide 12 text

# 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

Slide 13

Slide 13 text

# 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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

https://github.com/ruby/rbs

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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.

Slide 20

Slide 20 text

FAQ • Why it requires writing types in other f i les?

Slide 21

Slide 21 text

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.

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Releasing Gems with RBS • Add sig directory and put RBS f i les there.

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

@soutaro