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

Ruby3 is a typed language

Ruby3 is a typed language

Soutaro Matsumoto

April 17, 2020
Tweet

More Decks by Soutaro Matsumoto

Other Decks in Programming

Transcript

  1. Ruby3 is a Typed Language
    Soutaro Matsumoto

    View Slide

  2. Soutaro Matsumoto
    • Leads the design and implementation of RBS.

    • Develops a static type checker for Ruby “Steep”.

    • Software Engineer at Square.

    View Slide

  3. 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);

    View Slide

  4. 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);

    View Slide

  5. 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)

    View Slide

  6. 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)

    View Slide

  7. 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

    View Slide

  8. 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

    View Slide

  9. 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.

    View Slide

  10. 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

    View Slide

  11. 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)

    View Slide

  12. Conclusion
    • Start type checking your Ruby code today!

    View Slide

  13. Conclusion
    • Start type checking your Ruby code today!

    View Slide


  14. 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?

    View Slide

  15. 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

    View Slide

  16. 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

    View Slide

  17. 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.

    View Slide

  18. 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

    View Slide

  19. 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

    View Slide

  20. “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.

    View Slide

  21. Coding with Type Checker
    class Student
    end

    View Slide

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

    View Slide

  23. 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

    View Slide

  24. 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

    View Slide

  25. 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

    View Slide

  26. 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)

    View Slide

  27. 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)

    View Slide

  28. 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)

    View Slide

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

    View Slide

  30. 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

    View Slide

  31. 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

    View Slide

  32. 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

    View Slide

  33. 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

    View Slide

  34. 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

    View Slide

  35. Type Checking Options
    1. No type checking.

    2. Type checking using a static type checker.

    3. Between 1 and 2.

    View Slide

  36. 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.

    View Slide

  37. 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

    View Slide

  38. 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.

    View Slide

  39. 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

    View Slide

  40. 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

    View Slide

  41. 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

    View Slide


  42. 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?

    View Slide

  43. I don't want to write types.

    View Slide