what_methodsとは
what_methodsはメソッドの名前を忘れたとき調べるのに便利なライブラリ。
gem i what_methods
でインストールし、irbを立ち上げて次のように打ってみよう。
irb(main):001:0> require "what_methods" => true irb(main):002:0> [1,2,3].what? 3 [1, 2, 3].last == 3 [1, 2, 3].pop == 3 [1, 2, 3].length == 3 [1, 2, 3].size == 3 [1, 2, 3].count == 3 [1, 2, 3].sample == 3 [1, 2, 3].max == 3 => [:last, :pop, :length, :size, :count, :sample, :max]
sampleはいつも同じ結果が出てくるわけじゃないからなんだかなあ(汗)と言いたくなるけど、「××を調べる/変換するメソッドってなんだっけ?」という時に役立つライブラリであることが分かる。
目的
Rubyが好きなのでライブラリの一つでも書いてみたいな、と思っているのだけど、作り方もわからないしネタも思い浮かばないので、公開されているライブラリの中から興味を持ったものを読んで勉強していく。あとオブジェクトが持ってるメソッドを総当りしているのは分かるけどwhat_methodsの作り方すぐに思いつけなかったので。
what_methods.rbを読む
what_methodsはwhat_methods.rbというひとつのファイルから成り立っている。
コメントを除けば50行ほどの短いコードである。
これを頭から一行ずつ愚直に読み解く。
処理のおおまかな流れは、Objectクラスをオープンしてwhat?を追加し、その中でWhatMethods::MethodFinderクラスに自分(self)と受け取った引数が等価になるメソッドを検索させ(find)表示している(show)。
Objectクラスをオープンしているコードは次のようになっている。
class Object def what?(*a) WhatMethods::MethodFinder.show(self, *a) end #...省略... end
what?は可変長引数*aを受け取ってそれをselfとともにWhatsMethods::MethodsFinder.showに渡しているだけ。
ではshowは?
module WhatMethods class MethodFinder #...省略... def self.show( anObject, expectedResult, *args, &block) find( anObject, expectedResult, *args, &block).each { |name| print "#{anObject.inspect}.#{name}" print "(" + args.map { |o| o.inspect }.join(", ") + ")" unless args.empty? puts " == #{expectedResult.inspect}" } end end end
findに受け取った引数を渡して見つかったmethodsを表示しているだけのようだ。
表示のロジックの中にfindがあっていいのか…?
ともあれwhat_methodsの主要な処理はfindの中にありそう。
module WhatMethods class MethodFinder #...省略... def self.find( anObject, expectedResult, *args, &block ) stdout, stderr = $stdout, $stderr $stdout = $stderr = DummyOut.new # change this back to == if you become worried about speed and warnings. res = anObject.methods. select { |name| anObject.method(name).arity <= args.size }. select { |name| not @@blacklist.include? name }. select { |name| begin anObject.clone.method( name ).call( *args, &block ) == expectedResult; rescue Object; end } $stdout, $stderr = stdout, stderr res end #...省略... end end
一行目、$stdoutと$stderrをローカル変数に退避。
二行目、標準出力を利用するメソッドを呼び出した場合、余計な情報を画面に表示しないように、DummyOutという何もしないwriteメソッドをもつクラスを用意して標準出力を潰している。
class DummyOut def write(*args) end end
続く6行がもっともコアな処理。
res = anObject.methods. select { |name| anObject.method(name).arity <= args.size }. select { |name| not @@blacklist.include? name }. select { |name| begin anObject.clone.method( name ).call( *args, &block ) == expectedResult; rescue Object; end }
最初のselectでは引数の数を条件にnameを弾いている。
2番目のselectでは次の@@blacklistに載っているmethodを弾く。実行すると危なかったり処理がそれに持って行かれちゃう処理かな?
@@blacklist = %w(daemonize display exec exit! fork sleep system syscall what? ed emacs mate nano vi vim)
最後のselectで、残ったanObjectの持っているメソッドをすべて実行しexpectedResultと比較する。
cloneする理由が一瞬わからなかったが、これは破壊的なメソッドに備えているのだと気づいた(というかそれしかclone使う理由ってないかも)。
ちなみにcloneはimmutableなオブジェクトでは使えないため、Objectクラスで次のように拡張されている。
alias_method :__clone__, :clone def clone __clone__ rescue TypeError self end
最後に$stdout, $stderrを元に戻し、resを返してfindの処理は終わり。showに戻り以下略。
というわけで
いくつかのテクは新しく知ったけど、あまりモジュールの勉強にはならなかった気がする(ぉぃ
何か読むのにオススメのコードがあったら教えて下さい。