Slide 1

Slide 1 text

Ruby3 is a Typed Language Soutaro Matsumoto

Slide 2

Slide 2 text

Soutaro Matsumoto • Leads the design and implementation of RBS. • Develops a static type checker for Ruby “Steep”. • Software Engineer at Square.

Slide 3

Slide 3 text

What is a Typed Language • You can write types. • Type checker find problems. class Student { fullName: string; constructor(public firstName: string, public lastName: str this.fullName = firstName + " " + lastName; } } interface Person { firstName: string; lastName: string; } function greeter(person: Person): string { return "Hello, " + person.first_name + " " + person.lastNa } let user = new Student("Jane", "User"); document.body.textContent = greeter(user);

Slide 4

Slide 4 text

What is a Typed Language • You can write types. • Type checker find problems. class Student { fullName: string; constructor(public firstName: string, public lastName: str this.fullName = firstName + " " + lastName; } } interface Person { firstName: string; lastName: string; } function greeter(person: Person): string { return "Hello, " + person.first_name + " " + person.lastNa } let user = new Student("Jane", "User"); document.body.textContent = greeter(user);

Slide 5

Slide 5 text

Ruby 2.x class Student attr_reader :first_name attr_reader :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def full_name "#{first_name} #{last_name}" end end def greeter(peron) "Hello, #{person.first_name} #{person.last_name}" end user = Student.new("Jane", "User") puts greeter(user)

Slide 6

Slide 6 text

Ruby3 class Student attr_reader :first_name attr_reader :last_name def initialize(first_name, last_name) = @first_name, @last_name = first_name, last_name def full_name = "#{first_name} #{last_name}" end module Greeter def self.greet(peron) = "Hello, #{person.first_name} #{person.last_name}" end user = Student.new("Jane", "User") puts Greeter.greet(user)

Slide 7

Slide 7 text

Ruby3 class Student attr_reader :first_name attr_reader :last_name def initialize(first_name, last_name) = @first_name, @last_name = first_name, last_name def full_name = "#{first_name} #{last_name}" end module Greeter def greet(peron) = "Hello, #{person.first_name} #{person.last_name}" end user = Student.new("Jane", "User") puts Greeter.greet(user) class Student attr_reader first_name: String attr_reader last_name: String def initialize(String, String) -> void def full_name: () -> String end interface _Person def first_name: () -> String def last_name: () -> String end module Greeter def self.greet: (_Person) -> String end

Slide 8

Slide 8 text

RBS • Type declaration for Ruby programs. • No type annotation in Ruby code, but in different files. • Declare classes, modules, methods, ... class Student attr_reader first_name: String attr_reader last_name: String def initialize(String, String) -> void def full_name: () -> String end interface _Person def first_name: () -> String def last_name: () -> String end module Greeter def greet: (_Person) -> String end

Slide 9

Slide 9 text

Ruby3 • Ruby2 program == Ruby code
 Ruby3 program == Ruby code + RBS. • Ruby3 will ship with RBS (+ stdlib signatures). • RBS (+ stdlib signatures) will release as a gem for 2.7 soon. • Type checkers won't be a part of Ruby.

Slide 10

Slide 10 text

State of RBS • https://github.com/ruby/ruby-signature • No release yet. • Will rename to RBS ($ gem install rbs). • PR is open on ruby/ruby for migration. • PRs from Rubyists! • Improvements on .rbs files • Improvements on test features

Slide 11

Slide 11 text

RBS Ecosystem • Type checkers • Steep natively supports RBS. • type-profiler uses RBS (@mame) • Integration with existing tools/frameworks • Translation from/to Sorbet RBI (shopify/rbs_parser) • Rails support (pocke/rbs_rails)

Slide 12

Slide 12 text

Conclusion • Start type checking your Ruby code today!

Slide 13

Slide 13 text

Conclusion • Start type checking your Ruby code today!

Slide 14

Slide 14 text

Do we need to ship gems with RBS? Type checking from IDE? I don't want to type check my code. RBS is not DRY! My code is small. Cannot justify the extra cost for type checking. I don't want to write types. What if a gem doesn't provide RBS? How can I write RBS for this? How about duck typing?

Slide 15

Slide 15 text

RBS Expressiveness Union types Interface types Method overloading class Array[E] def []: (Integer) -> E | (Integer, Integer) -> Array[E] end Keyword args class Kernel private def gets: () -> (String | nil) # String? end interface _ToStr def to_str: () -> String end class String def +: (_ToStr) -> String end class Kernel private def clone: (?freeze: bool) -> self end

Slide 16

Slide 16 text

RBS Expressiveness • Supports • Duck typing (by interface types) • Open class • Union types • Method overloading • Doesn't support • Array#map! • Meta programming • Conditional mixin a = [1,2,3] a.map! { @1.to_s } class GPU include Win32 if os.windows? include Metal if os.mac? end attr_reader :user belongs_to :group attr_gzip :content

Slide 17

Slide 17 text

Type Checking Options 1. Type checking using a static type checker. 2. No type checking. 3. Between 1 and 2. Chose the best option for your project.

Slide 18

Slide 18 text

Using Static Type Checker • Pros • Fully utilize type checker • LSP based integrations provide better coding experience. • Cons • You have to take care of RBS • You have to write type-checking compatible code

Slide 19

Slide 19 text

Using Static Type Checker • Pros • Fully utilize type checker • LSP based integrations provide better coding experience. • Cons • You have to take care of RBS • You have to write type-checking compatible code

Slide 20

Slide 20 text

“My Code is Small” • 100 lines of Ruby code is worth type checking. • Small programs sometimes use generic data structures like Array/Hash. Let type checkers manage the data structure.

Slide 21

Slide 21 text

Coding with Type Checker class Student end

Slide 22

Slide 22 text

Coding with Type Checker class Student end sig/student.rbs

Slide 23

Slide 23 text

class Student attr_reader first_name: String attr_reader last_name: String def initialize(String, String) -> void def full_name: () -> String end sig/student.rbs

Slide 24

Slide 24 text

class Student attr_reader first_name: String attr_reader last_name: String def initialize(String, String) -> void def full_name: () -> String end sig/student.rbs lib/student.rb class Student attr_reader :first_name attr_reader :last_name def initialize(first_name, last_name) = @first_name, @last_name = first_name, last_name def full_name = "#{first_name} #{last_name}" end

Slide 25

Slide 25 text

class Student attr_reader first_name: String attr_reader last_name: String def initialize(String, String) -> void def full_name: () -> String end sig/student.rbs interface _Person def first_name: () -> String def last_name: () -> String end module Greeter def self.greet: (_Person) -> String end sig/greeter.rbs

Slide 26

Slide 26 text

class Student attr_reader first_name: String attr_reader last_name: String def initialize(String, String) -> void def full_name: () -> String end sig/student.rbs interface _Person def first_name: () -> String def last_name: () -> String end module Greeter def self.greet: (_Person) -> String end sig/greeter.rbs lib/main.rb user = Student.new("Jane", "User") puts Greeter.greet(user)

Slide 27

Slide 27 text

class Student attr_reader first_name: String attr_reader last_name: String def initialize(String, String) -> void def full_name: () -> String end sig/student.rbs interface _Person def first_name: () -> String def middle_name: () -> String? def last_name: () -> String end module Greeter def self.greet: (_Person) -> String end sig/greeter.rbs lib/main.rb user = Student.new("Jane", "User") puts Greeter.greet(user)

Slide 28

Slide 28 text

class Student attr_reader first_name: String attr_reader last_name: String def initialize(String, String) -> void def full_name: () -> String end sig/student.rbs interface _Person def first_name: () -> String def middle_name: () -> String? def last_name: () -> String end module Greeter def self.greet: (_Person) -> String end sig/greeter.rbs lib/main.rb user = Student.new("Jane", "User") puts Greeter.greet(user)

Slide 29

Slide 29 text

Coding with Type Checker • Design API before implementation. Design Doc Test Implementation Abstract Concrete Works (Revenue) Executable (pass/fail) Human readable (review)

Slide 30

Slide 30 text

Coding with Type Checker • Design API before implementation. Design Doc Test Implementation Abstract Concrete Works (Revenue) Executable (pass/fail) Human readable (review) Type check Verifiable

Slide 31

Slide 31 text

Type Check Partially • What if app already exists? • Type checkers allows mixing typed and untyped Ruby code. • App code vs test code. • Models vs controller. • This file cannot be typed. lib/student.rb eval <<-SRC class Student def method_missing(name, *args) case name when :first_name @first_name sig/student.rbs class Student attr_reader first_name: String attr_reader last_name: String def initialize: (String, String) -> void end

Slide 32

Slide 32 text

lib/person.rb class Student attr_reader :first_name attr_reader :last_name def initialize(first_name, last_name) = @first_name, @last_name = first_name, last_name def full_name = "#{first_name} #{last_name}" end lib/greeter.rb module Greeter def self.greeter(person) = "Hello, #{person.first_name} #{person.last_name}" end require "person" require "greeter" user = Student.new("Jane", "User") puts greeter(user) lib/main.rb

Slide 33

Slide 33 text

lib/person.rb class Student attr_reader :first_name attr_reader :last_name def initialize(first_name, last_name) = @first_name, @last_name = first_name, last_name def full_name = "#{first_name} #{last_name}" end lib/greeter.rb module Greeter def self.greeter(person) = "Hello, #{person.first_name} #{person.last_name}" end require "person" require "greeter" user = Student.new("Jane", "User") puts greeter(user) lib/main.rb sig/person.rbs class Student attr_reader first_name: String attr_reader last_name: String def initialize: (String, String) -> void def full_name: () -> String end

Slide 34

Slide 34 text

lib/person.rb class Student attr_reader :first_name attr_reader :last_name def initialize(first_name, last_name) = @first_name, @last_name = first_name, last_name def full_name = "#{first_name} #{last_name}" end lib/greeter.rb module Greeter def self.greeter(person) = "Hello, #{person.first_name} #{person.last_name}" end require "person" require "greeter" user = Student.new("Jane", "User") puts greeter(user) lib/main.rb sig/person.rbs class Student attr_reader first_name: String attr_reader last_name: String def initialize: (String, String) -> void def full_name: () -> String end sig/greeter.rbs interface _Person def first_name: () -> String def last_name: () -> String end module Greeter def self.greet: (_Person) -> Strin end

Slide 35

Slide 35 text

Type Checking Options 1. No type checking. 2. Type checking using a static type checker. 3. Between 1 and 2.

Slide 36

Slide 36 text

RBS without Type Checker • You write RBS but don't use type checker. • Pros • You can help your gem users type check their programs. • You don't have to write type-checker compatible code. • Cons • You cannot type check.

Slide 37

Slide 37 text

Writing RBS • You don't have to write perfect RBS. • List of methods/classes of untyped is okay. • Write methods you really need to type check your code. class CSV def self.read: (String) -> Array[Array[String]] end sig/polyfill/csv.rbs class IO def self.popen: (*untyped args) -> untyped end sig/polyfill/io.rbs

Slide 38

Slide 38 text

rbs prototype $ bundle exec rbs prototype runtime Integer class Integer < Numeric include JSON::Ext::Generator::GeneratorMethods::Integer def self.sqrt: (untyped) -> untyped public def %: (untyped) -> untyped def &: (untyped) -> untyped def *: (untyped) -> untyped Prints list of classes, modules, includes and methods. Replace untypeds with precise types.

Slide 39

Slide 39 text

Runtime Type Check • Opt-in runtime type check with RBS definition. • Technically possible, does anyone want this? $ RUBYOPT="-rbundler/setup -rrbs/test/runtime" \ RBS_RUNTIME_CHECK_TARGETS='Optcarrot::*' \ bundle exec rake test

Slide 40

Slide 40 text

Inline Type Annotations • A feature of type checkers. • [Steep] Not yet, but worth considering. class Student attr_reader :first_name # @type String attr_reader :last_name # @type String # @type (String, String) -> void def initialize(first_name, last_name) = @first_name, @last_name = first_name, last_name # @type () -> String def full_name = "#{first_name} #{last_name}" end

Slide 41

Slide 41 text

Inline Type Annotations • A feature of type checkers. • No plan yet, but worth considering. class Student < ApplicationRecord include Enumerable # including Enumerable[Student] # @type method classes: () -> ActiveRecord::Relation[Student] # @type method class_ids: () -> ActiveRecord::IDRelation has_many :classes # @type (University, classes: Array[ClassDefinition], requirements: Requirement, credits: Integer) -> Ar def minimum_classes_to_graduations(univ, classes:, requirements:, credits:) ... end end

Slide 42

Slide 42 text

Do we need to ship gems with RBS? Type checking from IDE? I don't want to type check my code. RBS is not DRY! My code is small. Cannot justify the extra cost for type checking. I don't want to write types. What if a gem doesn't provide RBS? How can I write RBS for this? How about duck typing?

Slide 43

Slide 43 text

I don't want to write types.