Ruby でメソッドを多重定義するためのライブラリをつくった
まあつくったのは結構前なんですが、つくりました。
Ruby は動的型付け言語なので、静的型付けのような型によるメソッドの多重定義は出来ないんですが、このライブラリによって静的型付けのようなメソッドの多重定義を実現する事ができます。
require "stitcher" using Stitcher class X # Fixnum オブジェクトのみ代入できる変数定義 stitcher_accessor value: Fixnum # Fixnum のみ受け取る stitcher_require [Fixnum] def add a value + a end # String のみ受け取る stitcher_require [String] def add a value + a.length end end x = X.new x.value = 42 # OK # x.value = "homu" # Error p x.add -3 # => 39 p x.add "homu" # => 46 # p x.add [1, 2, ] # Error
[gem-stitcher]
[インストール]
$ gem install stitcher
[これは何?]
これは、静的型付け言語のように定義する(定義した)メソッドに対して引数のシグネチャを設定するライブラリです。 シグネチャを指定することにより、静的型付け言語のようなメソッドの多重定義を行うことを目的としています。
[使い方]
stitcher
は定義したメソッドか、次に定義するメソッドに対してシグネチャを設定することで使用します。
require "stitcher" # using することで Module クラスに Stitcher の機能が mixin される using Stitcher class Counter attr_reader :value def initialize value @value = value end # 既存のメソッドに対して引数のシグネチャを設定する # #initialize メソッドに対して Fixnum クラスのインスタンスのみ受け取るようにする stitcher_register :initialize, [Fixnum] # stitcher_register を使用することで次に定義するメソッドに対しての # シグネチャを設定することもできる # stitcher_register の第二引数と stitcher_require の第一引数は同じである stitcher_require [Fixnum] # add メソッドは Fixnum のインスタンスのみ受け付ける def add value @value += value end # また、違うシグネチャを設定することにより複数のメソッドを定義する事ができる stitcher_require [String] def add str @value += str.to_i end end count = Counter.new 0 # OK # count = Counter.new "" # Error: Fixnum クラスのインスタンスではない count.add 10 # OK count.add "12" # OK # count.add 0.42 # Error p count.value # => 22
このように、
というに使い分けて利用します。
[呼び出されるメソッドの優先順位]
複数のメソッドにマッチした場合、後から定義したメソッドを優先して呼び出します。
require "stitcher" using Stitcher class X stitcher_require [Object] def func a "func{Object}" end stitcher_require [String] def func a "func{String}" end end x = X.new p x.func 10 # => "func{Object}" p x.func "homu" # => "func{String}"
ですので、クラス拡張でも利用することが出来ます。
require "stitcher" using Stitcher class Array stitcher_require [String] def at str at str.to_i end end p [1, 2, 3].at "1" # => 2 p [1, 2, 3].at 2 # => 3
[可変長引数]
Array#+@
で可変長引数になります。
require "stitcher" using Stitcher class X def initialize step @step = step end # String を1つ以上受け取る可変長引数 stitcher_require +[String] def join *args args.join @step end end x = X.new ", " p x.join "homu", "mami", "mado" # => "homu, mami, mado" # p x.join # Error
0個以上の可変長引数を受け取りたい場合は stitcher_require []
と組み合わせてください。
[ブロック引数の有無をチェック]
[String] & Stitcher::Concepts.blockable
でブロック引数を要求します。
require "stitcher" using Stitcher class X stitcher_require [String] def func str str + str end stitcher_require [String] & Stitcher::Concepts.blockable def func str yield str end end x = X.new p x.func "homu" # => "homuhomu" p x.func("mami"){ |str| str + "homu" } # => "mamihomu"
[アクセッサ]
stitcher_accessor
でアクセッサを定義する事が出来ます。
require "stitcher" using Stitcher class Person # String のみ代入できる name と # Fixnum のみ代入できる age のアクセッサを定義 stitcher_accessor name: String, age: Fixnum end homu = Person.new homu.name = "homu" # OK # homu.name = 42 # Error homu.age = 14 # OK homu.instance_eval { @age = "homu" } # OK @ 変数への代入は抑制できない
他にも書き込み専用であれば stitcher_writer
が利用できます。
[クラスオブジェクト以外の利用]
基本的にはクラスオブジェクトのリストを使用しますが、内部ではシグネチャのチェックに #===
を利用しているので、各シグネチャには #===
が定義されているオブジェクトであれば何でも渡すことができます。
なので、Proc
や Regexp
オブジェクトなどをシグネチャとして渡すこともできます。
class Http # Regexp オブジェクトを設定 stitcher_require [/^https?/] def get url # ... end # Proc オブジェクトを設定 stitcher_require [ proc { |hash| (Hash === hash && hash.key?(:url)) } ] def get hash get hash[:url] end end http = Http.new http.get "http://docs.ruby-lang.org/" # OK http.get({ url: "http://docs.ruby-lang.org/" }) # OK http.get "ftp://docs.ruby-lang.org/" # Error http.get({ uri: "http://docs.ruby-lang.org/" }) # Error
このあたりは case 文と同じ構想になります。
何か不具合等があれば Issues までおねがいします。