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

Ruby Hack Challenge Holiday #8

Ruby Hack Challenge Holiday #8

Soutaro Matsumoto

November 10, 2019
Tweet

More Decks by Soutaro Matsumoto

Other Decks in Programming

Transcript

  1. Writing types for Ruby
    Soutaro Matsumoto

    @soutaro (Square)
    Ruby Hack Challenge Holiday #8

    View full-size slide

  2. Outline
    • RBS in a nutshell

    • Testing signatures

    View full-size slide

  3. RBS in a nutshell
    • A language to describe types of Ruby programs

    • Classes, modules, and their relations (inheritance and mixin)

    • Types of methods and instance variables

    View full-size slide

  4. module Goodcheck
    def self.logger: -> Logger
    end
    class Goodcheck::Location
    attr_reader start_line: Integer
    attr_reader start_column: Integer
    attr_reader end_line: Integer
    attr_reader end_column: Integer
    def initialize: (start_line: Integer, start_column: Integer, end_line: Integer, end_column: Integer) -> void
    end
    Goodcheck::VERSION: String

    View full-size slide

  5. module Goodcheck
    def self.logger
    @logger ||= ActiveSupport::TaggedLogging.new(Logger.new(STDERR)).tap do |logger|
    logger.push_tags VERSION
    logger.level = Logger::ERROR
    end
    end
    end
    module Goodcheck
    def self.logger: -> Logger
    end

    View full-size slide

  6. Types
    Integer, ::Integer, Array[::Symbol] (instance types)
    singleton(Integer), singleton(Array) (singleton types)
    A, B, C (type variables)
    ::_Each[Integer, void] (interface types)
    t, Goodcheck::patterns (alias types)
    untyped, void, nil, bool (base types)
    self (self type)
    1, "str", :foo, true, false (literal types)
    Integer? (optional types)
    t_1 | t_2, t_1 & t_2 (union, intersection types)
    [t_1, ...], { id: Integer, name: String} (tuple, record)
    ^(Integer) -> void (proc types)

    View full-size slide

  7. Method types
    (Integer, ?Integer, *String, Symbol) -> void
    (Integer i, ?Integer? j, *String | Symbol xs, true) -> void
    (id: Integer, ?name: String?, **untyped) -> (Integer | String)
    (Integer id, id: Integer foo,**untyped) -> (Integer | String)
    (id: Integer id, **untyped rest) -> bool
    [A] () { (self) -> A } -> A
    () ?{ (Integer) -> void } -> void

    View full-size slide

  8. Class, module, interface
    class Person < Object
    attr_reader name: String
    def initialize: (name: String) -> void
    | (Person other) -> void
    def each_siblings: () { (String) -> void } -> void
    end

    View full-size slide

  9. Including a module
    class Person[A] < Object
    attr_reader name: A
    include Comparable
    extend Registory[Person]
    def initialize: (name: A) -> void
    def each_siblings: () { (String) -> void } -> void
    def <=>: (untyped) -> Integer
    end

    View full-size slide

  10. Including interface
    class Person < Object
    attr_reader name: String
    include Comparable
    extend Registory[Person]
    include _Hashable
    def initialize: (name: String) -> void
    def each_siblings: () { (String) -> void } -> void
    def <=>: (untyped) -> Integer
    end
    interface _Hashable
    def hash: () -> Integer
    def eql?: (untyped) -> bool
    end

    View full-size slide

  11. Q & A
    • Complete syntax guide: doc/syntax.md

    • What is bool? When to use it?

    • It accepts any types, not only TrueClass and FalseClass.

    • How the class name resolution is?

    • It tries to simulates Ruby. One big difference is: in RBS writing qualified
    class name pushes new namespace

    View full-size slide

  12. Testing Signatures
    • Signatures (types) are intended to be used in static type checker

    • However, we can use them in runtime!

    View full-size slide

  13. $ RUBYOPT='-rbundler/setup -rruby/signature/test/setup' \
    RBS_TEST_TARGET='Goodcheck::*' \
    RBS_TEST_RAISE=true \
    RBS_TEST_OPT='-rset -rpathname -Isig' \

    bundle exec rake test

    View full-size slide

  14. Runtime Testing Semantics
    • Class instance types: based on #is_a? result

    • Class singleton types: based on #== result

    • Interface types: no testing!

    • Type variables: no testing!

    • Generics: only limited supports for Hash and Array

    View full-size slide

  15. Testing Your Program
    • The test is implemented via Module#prepend

    • Instruments all method calls and makes the test two times slower!

    • The RBS testing is non-intrusive (Nothing to be changed in your test code)

    1. Write signature

    2. Run unit test with RBS loaded (configure via environment variables)
    $ RUBYOPT='-rbundler/setup -rruby/signature/test/setup' \
    RBS_TEST_TARGET='Goodcheck::*' \
    bundle exec rake test

    View full-size slide

  16. Testing Stdlib
    • The test is implemented by refinements

    • You have to call methods in subject from test classes
    class BasicObjectTest < StdlibTest
    target BasicObject
    using hook.refinement
    def test_instance_eval
    BasicObject.new.instance_eval { 3 }
    end
    end $ ruby bin/test_runner.rb BasicObject

    View full-size slide