Slide 1

Slide 1 text

Writing types for Ruby Soutaro Matsumoto @soutaro (Square) Ruby Hack Challenge Holiday #8

Slide 2

Slide 2 text

Outline • RBS in a nutshell • Testing signatures

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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)

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Testing Signatures • Signatures (types) are intended to be used in static type checker • However, we can use them in runtime!

Slide 13

Slide 13 text

$ 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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Demo