https://bugs.ruby-lang.org をなんとなく眺めていたら、とあるチケットが目につきました。
このチケットは、「Method classは便利だけど記述がめんどくさいから新しい記法作ろうぜ」的なものです。
Method classはデバッグやblockとして使うのに便利ですよね。
foo.method(:bar).arity #=> 1 foo.method(:bar).source_location #=> ["file.rb", 25] Dir["*/*.rb"].map(&File.method(:basename)) #=> ["file.rb"]
Method classの欠点
僕が考えるMethod class唯一の欠点は「インスタンスを作るときにメソッドの前後に何かを書かなきゃいけない」だと思います。
# bar methodはどこで定義されてるんだろう? foo.bar # 前後にコードを書いて foo.method(:bar).source_location #=> ["file.rb", "25"] # わかったから前後のコードを削除 foo.bar
サッと知りたいだけのとき、これは不便です。
methods
なら最後だけ、tap
なら好きな場所に一箇所書くだけなので書きやすいですよね。
foo.methods #=> 後ろに付け足すだけ foo.tap { |i| p i}.bar #=> 真ん中に書くだけ
解決案
そこで考えた解決案として、「前後ではなく、前か後ろ片方だけなら楽なのでは」というものでした。
# あとに書く foo.bar.method.source_location # まえに書く foo.method.source_location.bar
あとに書くのは文法的に苦しそうなので、まえに書く方法を提案してみたのが先ほどのチケットのコメントというわけです。
これなら新しい文法を追加することなく、前後へのコードの追加から前だけのコードの追加になるので、 ある程度手間を軽減できそうです。
LazyMethod
どうもGemを書くのが趣味みたいなところがあるので、これをGem化したのがLazyMethodです。
名前は、「まだメソッド名を決めてないからUnboundMethodにあやかって、UndecidedMethod?UnnamedMethod?UnfoundMethod?かな?でもなんか地味でイケてる感じがない……。」と思いましたが、 呼び方にEnumerable#lazyっぽい気がなんとなく感じ取れたので「LazyMethod」としました。
使い方は、Kernel#method
メソッドの呼び方をfoo.method(:bar)
からfoo.method.bar
とするだけです。
引数をつけると、通常のKernel#method
メソッドの動作になります。
Object.instance_methods
に含まれるような基本的なメソッドはLazyMethodと相性が悪いです。
foo.method.to_s
のように使うと、文字列がほしいのかto_s
メソッドの情報がほしいのかで判断が別れるのですが、
こういう基本的なメソッドを書き換えると混乱を生んだりデバッグしにくかったりと、Methodオブジェクトとして使えてもあまり嬉しい要素がないので、
通常のRubyっぽい動きに舵を切りました。
また、source_location
やparameters
などのMethod classのメソッドは使われることが多いでしょう。
ところがfoo.method.bar.source_location
のように書いては元の問題が解決できないので、
foo.method.source_location.bar
のように遅延的に呼び出すことができるようにしました。
require 'lazy_method' foo.method #=> #<LazyMethod foo> foo.method(:bar) #=> #<Method foo:bar> foo.method.bar #=> #<Method foo:bar> foo.method(:bar).source_location #=> ["file.rb", 25] foo.method.bar.source_location #=> ["file.rb", 25] foo.method.source_location #=> #<LazyMethod foo(source_location> foo.method.source_location.bar #=> ["file.rb", 25] foo.method.source_location.baz #=> ["file.rb", 29] Dir["*/*.rb"].map(&File.method.basename) #=> ["file.rb"]
なんとなく便利っぽい気がしてきませんかね。僕はまだわかりません。