49
Ruby with types Soutaro Matsumoto

Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

  • Upload
    others

  • View
    4

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Ruby with types

Soutaro Matsumoto

Page 2: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

• From Tokyo, Japan

• Working for type of Ruby since 2004

Soutaro Matsumoto

Page 3: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Outline

• The overview of type checking for Ruby3

• Type checking benefits

• Steep quick tour

Page 4: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise
Page 5: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Ruby3 type checking• Detecting types of each Ruby expression statically to help developments• Performance is not our goal (it needs much more precise analysis)

taiwan = Conference.find_by!(name: "RubyConf Taiwan", year: 2019) taiwan.talks.each do |talk| puts talk.speaker.email end

Page 6: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Ruby3 type checking• Detecting types of each Ruby expression statically to help developments• Performance is not our goal (it needs much more precise analysis)

taiwan = Conference.find_by!(name: "RubyConf Taiwan", year: 2019) taiwan.talks.each do |talk| puts talk.speaker.email end

::Conference

Page 7: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Ruby3 type checking• Detecting types of each Ruby expression statically to help developments• Performance is not our goal (it needs much more precise analysis)

taiwan = Conference.find_by!(name: "RubyConf Taiwan", year: 2019) taiwan.talks.each do |talk| puts talk.speaker.email end

::Conference

() { (::Talk) -> void } -> self

Page 8: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Ruby3 type checking• Detecting types of each Ruby expression statically to help developments• Performance is not our goal (it needs much more precise analysis)

taiwan = Conference.find_by!(name: "RubyConf Taiwan", year: 2019) taiwan.talks.each do |talk| puts talk.speaker.email end

::String

::Conference

() { (::Talk) -> void } -> self

Page 9: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Ruby & types projectFrom 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 discuss for collaboration, share the progress, and develop the foundation of type checking for Ruby

Page 10: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Sub projects

type-profiler

Steep

Sorbet

RDL

RBSruby-signature

Level 1 type checker Level 2 type checkers

Type signature language

Page 11: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Sub projects

type-profiler

Steep

Sorbet

RDL

RBSruby-signature

Level 1 type checker Level 2 type checkers

Type signature language

Use/Generate

Use

Page 12: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Level 1 type checker

• Type checking without any extra efforts• No type annotation, no signature of your program• Reads Ruby code without type annotations and infers the types as

much as possible

• Many expressions will be left untyped• No bugs can be found around the untyped expressions

• Minimizing the cost for type checking sacrificing the precision/coverage

Page 13: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Level 2 type checkers• You write inline type annotations as embedded DSL or comments• Detects types of most of Ruby expressions

Sorbet Steep

class Box extend T::Sig extend T::Generic

Elem = type_member

sig {returns(Elem)} attr_reader :x

sig {params(x: Elem).returns(Elem)} attr_writer :x end

box = Box[Integer].new box.x = "hello"

class Box # @dynamic x attr_accessor :x end

# @type var box: Box[Integer] box = Box.new box.x = "hello"

class Box[X] attr_accessor x: X end

Page 14: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

The Ruby signature language

• Ruby-like but different syntax

• Defines the structure of Ruby programs

• Classes, modules, mixin, and interfaces

• Methods and instance variables

• Generics, unions, tuples, optionals, ...

• You can write types for most of the Ruby programs

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

Page 15: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Sub projects

type-profiler

Steep

Sorbet

RDL

Level 1 type checker Level 2 type checkers

Type signature language

RBSruby-signature

Page 16: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Sub projects

type-profiler

Steep

Sorbet

RDL

Level 1 type checker Level 2 type checkers

Type signature language

Will be part of Ruby3

RBSruby-signature

Page 17: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Type checking spectrum

No type checking Type check your program with signature

Page 18: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Type checking spectrum

No type checking Type check your program with signature

Type check your program with library signature

Page 19: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Type checking spectrum

No type checking Type check your program with signature

Type check your program with library signature

Auto-generate type signature

Page 20: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Type checking spectrum

No type checking Type check your program with signature

Type check your program with library signature

Write signature but no type checking

Auto-generate type signature

Page 21: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

No type checking

!

Page 22: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Type check with library signature• You don't write signatures of your Ruby program

• Use library signatures and infer the types of your Ruby code• You can try with level 1 type checker (type-profiler)• Level 2 type checkers detects some trivial problems (Sorbet, Steep)

taiwan = Conference.find_by!(name: "RubyConf Taiwan", year: 2019) taiwan.talks.each do |talk| puts talk.email end Missing #speaker call

Page 23: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Auto generate type signature• You don't write signatures of your Ruby program

• Let type-profiler generate type signature of your Ruby program

class Fib def fib(x) if x <= 1 x else fib(x-1) + fib(x-2) end end end

puts Fib.new.fib(30)

Fib#fib :: (Integer) -> Integer

Page 24: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Write signature but no type checking• Write type signature of your program to ship it with the gem

• Run type-profiler with your tests to detect mismatches between your signature and tests

class Fib def fib: (Integer) -> Integer end

class FibTest < Minitest::Test def test_fib assert_equal 2, 3 assert_equal "two", Fib.new.fib("three") assert_equal "二", Fib.new.fib("三") end end

Page 25: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Write signature but no type checking• Write type signature of your program to ship it with the gem

• Run type-profiler with your tests to detect mismatches between your signature and tests

class Fib def fib: (Integer) -> Integer end

class FibTest < Minitest::Test def test_fib assert_equal 2, 3 assert_equal "two", Fib.new.fib("three") assert_equal "二", Fib.new.fib("三") end end

class Fib def fib: (Integer) -> Integer | (String) -> String end

Page 26: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Type check your code• Prepare the signature of your Ruby program and type check the implementation• You may write the signature manually• You may generate the signature with type-profiler

class Box # @dynamic x attr_accessor :x end

# @type var box: Box[Integer] box = Box.new box.x = "hello"

Will require some inline type annotations

Page 27: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Type checking spectrum

No type checking Type check your program with signature

Type check your program with library signature

Write signature but no type checking

Auto-generate type signature

Page 28: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Type checking spectrum

No type checking Type check your program with signature

Type check your program with library signature

Write signature but no type checking

Auto-generate type signature 😀

Page 29: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Outline

• The overview of type checking for Ruby3

• Type checking benefits

• Steep quick tour

Page 30: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Type checking benefits

• Development experience improvements• Uncover bugs without running tests• Documentation with validation• Code navigations

• Application quality improvements• Uncover bugs missed in tests• To help the development of advanced analyses

Page 31: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

NoMethodError (undefined method `update!' for nil:NilClass)

Page 32: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

conference = Conference.find_by(name: "RubyConf Taiwan") conference.update!(year: 2019)

Page 33: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

conference = Conference.find_by(name: "RubyConf Taiwan") conference.update!(year: 2019)

(name: String) -> (Conference | nil)

Page 34: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

conference = Conference.find_by(name: "RubyConf Taiwan") conference.update!(year: 2019)

Conference | nil

(name: String) -> (Conference | nil)

Page 35: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

conference = Conference.find_by(name: "RubyConf Taiwan") conference.update!(year: 2019)

Conference | nil

(name: String) -> (Conference | nil)

NoMethodError (undefined method `update!' for nil:NilClass)

Page 36: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

conference = Conference.find_by(name: "RubyConf Taiwan") if conference conference.update!(year: 2019) end

conference = Conference.find_by!(name: "RubyConf Taiwan") conference.update!(year: 2019)

Test if it is nil before using the value

Use find_by! instead and abort if there is no record

Page 37: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

conference = Conference.find_by(name: "RubyConf Taiwan") send_notification_to_attendees(conference)

Conference | nil

Page 38: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

conference = Conference.find_by(name: "RubyConf Taiwan") send_notification_to_attendees(conference)

Conference | nil

(Conference) -> void

(Conference | nil) -> void 🤔

Page 39: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Working with nils safely• The only way to avoid nil dereference error is by testing if it is nil or not• What if your teammate changes a value to nilable?

• Type checkers will tell you if you forget the test

• We can generalize to any union typestype json = nil | Numeric | String | TrueClass | FalseClass | Array[json] | Hash[String, json]

• Best fit for case / case-in

Page 40: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Outline

• The overview of type checking for Ruby3

• Type checking benefits

• Steep quick tour

Page 41: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Steep

Key ideas • Structural subtyping (for duck typing)• Doesn't change runtime behavior at all

$ gem install steep

https://github.com/soutaro/steep

Page 42: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

class Conference attr_reader :name attr_reader :talks def initialize(name:) @name = name @talks = [] end

def speakers talks.each(&:speaker) # Should be #map call end end

conference = Conference.new("RubyConf Taiwan") # Should be a keyword argument conference.talks << Talk.new(...)

Page 43: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

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("RubyConf Taiwan") conference.talks << Talk.new(...)

Page 44: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

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("RubyConf Taiwan") 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=singleton(::Conference), expected={ :name => ::String }, actual=::String

Page 45: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

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("RubyConf Taiwan") 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]

Page 46: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

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)

Page 47: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

No runtime invasion

• Inline type annotations of Steep are comments

• Better for libraries:• Your library users would not want to install Steep• You can keep # of runtime dependencies as small as possible

spec.add_development_dependency "steep"

Page 48: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

Recap

• Plan for Ruby3 type checking• We provide several options to adopt type checking

• Level 2 type checkers will make Ruby more powerful• Helps to handle nils safely

• Best fit for case / case-in

• Steep is the best option for Ruby type checking [my personal opinion]

Page 49: Ruby with types€¦ · Ruby3 type checking • Detecting types of each Ruby expression statically to help developments • Performance is not our goal (it needs much more precise

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