Probe Object
ハッカーと画家の中で、「これからの言語はプロファイラが重要だ」に影響されて、おもいついたネタ。
Rubyでは動的型付けの悪影響で、ライブラリなんかの関数に何のオブジェクトを渡せばいいのかさっぱりわからないことがある。ソース読んでも関数定義だけでは仮引数の個数と名前くらいしかわかんない。
てなかんじ。
そこで僕なんかは、Rubyコードの型推論をやってくれちゃうid:soutaroさんのTypingRuby(http://truby.sourceforge.jp/index.j.html)なんかに期待してしまうわけだが、今回は逆のアプローチを思いついたよ!というお話。
以下長いので、ここでおる。
実行時にいろいろやるのが得意なRubyなんだから、とにかく走らせてみればいいのだ。何をわたすべきかわかんない関数でも、オブジェクトを渡して実行しちゃう。んでもって、そのオブジェクトは、自身に対して呼ばれたメソッド名を自分で記録するような仕組みをしこんでおく。どうせどっかで例外なりエラーなりが起こるだろうから、そのときにオブジェクトの中身を見てやれば、少なくともそのオブジェクトに期待されているメソッドがわかるんじゃなかろか?
と思って、そういう働きをするものをProbe Objectと名づけてささっとスケッチしてみた。こんな感じ。
module Probe_mod def initialize @list = {} @name = self.class.to_s end def __list @list end def __def_class(newclassname) eval <<EOS class #{newclassname} include Probe_mod end EOS end def method_missing(name,*arg) if @list[name] == nil newclassname = "#{@name}_#{name.to_s}" newobj = nil __def_class(newclassname) eval("newobj = #{newclassname}.new") @list[name] = [newobj,[]] end @list[name][1] << arg.map {|item| item.class} return @list[name][0] end end class Probe include Probe_mod end
Probeオブジェクトは、さっきも書いたように自分に対して呼ばれたメソッド名を保持している。このへんmethod_missingを使ってどういうメソッドが呼ばれても反応するようにしてある。どう反応するかというと、「Probe_呼ばれたメソッドの系列」 というクラスを新たにつくって、そのインスタンスを返す。自分は呼ばれたメソッド名をキーに返り値用に作ったインスタンスと、渡された引数のクラス名を保持する。
つまり、Probeオブジェクトの中身を見れば、
- どのようなメソッドが呼ばれたか
- そのときの引数に渡されたクラスは何か?
- さらにそのメソッドの返り値がどう使われるのか?
がわかるようになってる。最後のがわかるのは、再帰的にProbeオブジェクトを返してるからだ。ちなみに、例外が起こるときにクラス名が提示されることが多いようなので、クラス名を見るだけでどういうメソッド呼びだし系列で生成されたオブジェクトなのかわかるようにしてみた。
使い方はこう。あまりいいサンプルが思いつかなかったので、webrickでサンプルのためのサンプルを作ってみた。
require 'webrick' s = WEBrick::HTTPServer.new(:Port=>2000) test = Probe.new s.mount('/test',test) require 'pp' trap("INT"){ pp test s.shutdown } s.start
Webrickの動かし方(startするとか)は知っているのに、mountの第二引数に何を渡せばいいかわかんないケース。ちょっと無理な仮定だ。無理無理だ。ごめんなさい。
これを走らせるとWebrickはサーバとして動作するので、localhost:2000にアクセスしてみる。何もいわれない。適当なとこでCtrl-Cでwebrickをストップさせると、Probeオブジェクトの中身がppでざざっと表示される。
こんなかんじ。
#<Probe:0x402a23c8 @list= {:get_instance=> [#<Probe_mod::Probe_get_instance:0x40335c4c @list= {:service=> [#<Probe_mod::Probe_get_instance_service:0x40335904 @list={}, @name="Probe_mod::Probe_get_instance_service">, [[WEBrick::HTTPRequest, WEBrick::HTTPResponse], [WEBrick::HTTPRequest, WEBrick::HTTPResponse]]]}, @name="Probe_mod::Probe_get_instance">, [[WEBrick::HTTPServer], [WEBrick::HTTPServer]]]}, @name="Probe">
ちょっとみづらいが、@listがハッシュになってて、もろもろの情報がはいってる。
これを見るとまずわかるのが、Probeオブジェクトのget_instanceというメソッドが呼ばれて、その時の引数はWEBrick::HTTPServerだということ。
でもって、そのとき返したオブジェクトはProbe_get_instanceという名前のクラスのインスタンスになるので、その中身をみてやる。こいつの@listも同じように解釈できて、serviceというメソッドが、WEBrick::HTTPRequestとWEBrick::HTTPResponseという引数で呼ばれていることがわかる。
さらに、serviceメソッドの返り値で渡したProbe_get_instance_serviceオブジェクトの@listをみると、これは空だ。たぶん、なにも使われてないんだろう。
ここまでで、だいたいWEBrick::HTTPServerの挙動は想像できる。
- get_instanceが呼ばれるのでシングルトンを期待している。つまり本来mountの引数に渡すのはクラスだろう。
- get_instanceで生成されるインスタンスに対して、serviceメソッドが呼ばれる。
- serviceメソッドの引数はRequestとResponseが渡される。いかにもserviceメソッドの中でRequestの情報を使ってResponseのbodyをほげほげしてやれば動作しそう。
本来はmountの引数にはWEBrick::HTTPServlet::AbstractServletを継承したクラスを渡すことになっているのだが、シングルトンのところは無視して*1上の予測に従って、Probeをこう書きかえてやると、、
class Probe_Dummy include Probe_mod def get_instance(server) @server = server return self end def service(req,res) res.body = 'hoge' end end
なんとlocalhost:2000にアクセスすると、ちゃんとhogeと表示されてしまう。ブラボー!Duck Typing!
もうちょっと考えると、requireでとりこんだクラス群のメソッド総チェック、なんてのにも使えそう。新規に追加されたクラスをかたっぱしからnewして、もってるpublicメソッドにこのProbeをつっこんで実行させまくる、とか。
問題は例外が発生したときにどうするか、かなあ。
それに、サンプルとしてwebrickはちょっと特殊すぎるので、効果がいまいち疑わしい。サンプルとして使えそうなライブラリを思いついた方はコメントお願いします。
あれ…どこがプロファイラなんだろう…