ruby with types€¦ · ruby3 type checking • detecting types of each ruby expression statically...
TRANSCRIPT
![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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/1.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/2.jpg)
• 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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/3.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/4.jpg)
![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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/5.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/6.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/7.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/8.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/9.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/10.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/11.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/12.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/13.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/14.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/15.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/16.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/17.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/18.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/19.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/20.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/21.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/22.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/23.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/24.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/25.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/26.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/27.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/28.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/29.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/30.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/31.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/32.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/33.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/34.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/35.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/36.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/37.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/38.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/39.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/40.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/41.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/42.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/43.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/44.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/45.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/46.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/47.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/48.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022042409/5f24e55968902772b5717c96/html5/thumbnails/49.jpg)
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