Rubypeを使ってRubyで型のメリットを享受する

  • 23
    Like
  • 2
    Comment
More than 1 year has passed since last update.

Ruby + Type = Rubype

既存のコードとの互換性を崩すことなく、選択的にメソッドに型保証を与えるGemです.

210414.png

Github
HP
HackerNews

ここで指す'メソッドの型保証'とは実行時レベルでメソッドの引数返値のクラス型や反応すべきメソッドを保証する事です.

得られる一般的な型保証のメリット

Executableなドキュメントをコード内に付与出来る.
エラーがより意味のあるエラーになる.
Rubypeが確かであるならば引数返値チェックの処理は一切不要となる

この様な形で型情報付与するからこそ出来ること.

実行時に動的に型の付与変更、型情報の確認がする事も出来る.(型情報もオブジェクト)
クラス型のみではなく、反応すべきメソッドをシンボルで指定する事が可能

このGemのデメリット、至らぬところ

当該メソッドを呼び出す度に型チェックを行うのでオーバヘッドが生まれる.
(しかし多くの場合は気にされるほどではない)

実行前に行われるいわゆる型チェックによるメリットは享受出来ない.

選択的にメソッドの引数と返値のクラスを保証

型保証を与えないメソッドとの共存が可能で漸進的に型情報を付与していく事が可能です.

require 'rubype'

class MyClass
  # `sum`が2つの`Numeric`オブジェクトを取り、`String`オブジェクトを返す事を保証する
  def sum(x, y)
    (x + y).to_s
  end
  typesig :sum, [Numeric, Numeric] => String
end

MyClass.new.method(:sum).type_info
#=> [Numeric, Numeric] => String

MyClass.new.new(1, 2)
#=> '3'

# 
MyClass.new.sum(1, '2')
#=> Rubype::ArgumentTypeError: Expected MyClass#sum's 2nd argument to be Numeric but got "2" instead

Any型やシンボルを使えばダックタイピングも可能

class People
  def marry(people)
    # 何を返値にしても大丈夫です!
  end
  typesig :marry, [People] => Any
end

#to_i に必ず反応する事を保証

class MyClass
  def sum(x, y)
    x.to_i + y
  end
  typesig :sum, [:to_i, Numeric] => Numeric
end

MyClass.new.sum(:has_no_to_i, 2)
#=> Rubype::ArgumentTypeError: Expected MyClass#sum's 1th argument to have method #to_i but got :has_no_to_i instead

明快!

class People
  def marry?(people)
    'not boolean'
  end
  typesig :marry?, [People] => Boolean
end

People.new.marry?(People.new)
#=> Rubype::ReturnTypeError: Expected People#marry? to return Boolean but got "not boolean" instead

型情報もオブジェクトなので動的に付与変更確認する事も出来る.

動的な型の付与や変更の意味は確かでないですが、実行中に型情報を確認出来ることはメリットだと思います.

require 'rubype'

class MyClass
  def string?(x)
    x.is_a?(String)
  end
  typesig :string?, [Any] => Boolean
end

typed_method = MyClass.new.method(:string?)

if typed_method.return_type == Boolean
  # メソッドの型情報をコードに組み込む事ができる.
  ...
end

ドキュメント

Gemの使用者が使用すると想定されるメソッドを以下の4つ

Module.typesig

Method#type_info

Method#arg_types

Method#return_type

Module.typesig

型情報を付与する.
typesig(メソッド名, 型情報)

class MyClass
  def typed_method(arg)
    1
  end
  typesig :typed_method, [String] => Fixnum
end

型情報の一般形は

[(第一引数の型), (第二引数の型) ... (第n引数の型)] => (返値の型)

として与えられる.

この文脈でとはオブジェクトのクラスまたは反応されるべきメソッドのどちらか.

同じメソッドに対してtypesigが二度以上定義されたならば、より後に定義されたもののみを扱う.

Method#type_info

型情報をハッシュで返す

class MyClass
  def typed_method(arg)
    arg.to_i
  end
  typesig :typed_method, [:to_i] => Fixnum
end

MyClass.new.method(:typed_method).type_info
#=> {[:to_i]=>Fixnum}

Method#arg_types

引数の型情報を配列で返す.

class MyClass
  def typed_method(arg)
    arg.to_i
  end
  typesig :typed_method, [:to_i] => Fixnum
end

MyClass.new.method(:typed_method).arg_types
#=> [:to_i]

Method#return_type

返値の型情報を返す.

class MyClass
  def typed_method(arg)
    arg.to_i
  end
  typesig :typed_method, [:to_i] => Fixnum
end

MyClass.new.method(:typed_method).return_type
#=> Fixnum

Rubypeが定義する追加クラスとしては

  • どんなオブジェクトに対しても型情報としてマッチするAnyクラス
  • TrueClassとFalseClassのオブジェクトに型情報としてマッチするBooleanクラス

エラーは

module Rubype
  class ArgumentTypeError < ::TypeError; end
  class ReturnTypeError   < ::TypeError; end
end

の2つ

まだまだ始まったばかりなので

一緒にやってくれる人をガンガン募集しています!

みんなでワイワイとやりましょう〜!

https://github.com/gogotanaka/Rubype

210414.png