Ruby Hack Challenge Holiday #8

Ruby Hack Challenge Holiday #8

1fab9d01b25e99522f3dfd01e3d4cb51?s=128

Soutaro Matsumoto

November 10, 2019
Tweet

Transcript

  1. Writing types for Ruby Soutaro Matsumoto @soutaro (Square) Ruby Hack

    Challenge Holiday #8
  2. Outline • RBS in a nutshell • Testing signatures

  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
  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
  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
  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)
  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
  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
  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
  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
  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
  12. Testing Signatures • Signatures (types) are intended to be used

    in static type checker • However, we can use them in runtime!
  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
  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
  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
  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
  17. Demo