Rubyメタプログラミング事始め その2
前回では、メタプログラミングって言うのが一体何者なのかを考えてみたんですが、
あまり面白くなかったので、今回は哲学的に考えるのではなく実際に手を動かすことで、Rubyによるメタプログラミングを考えてみようっと。
参考資料はまつもと直伝 プログラミングのオキテです。
ぶっちゃけてしまうと、参考資料とほとんど変わらないことをするので、より洗練されている上のリンクを読んだ方がいいかも。
さてメタプログラミングと言うことでプログラムのためのプログラムの第一弾として選んだものは、attrシリーズです。
attr_accsessorは実行時に動的にgetterとsetterを作成してくれる機能だと思っていたのですが、じつはこれはRuby上で定義されたメソッドだと言うことが分かりました。
ということで、今回はgetterを作ってくれるattr_readerの再実装をしてみようっと。
さて、参考資料内でまつもとゆきひろさんはgetterとsetterを作ってくれるattr_accsessorを下のように実装をしていました。
class Module def attr_accessor(*syms) syms.each do |sym| class_eval %{ def #{sym} @#{sym} end def #{sym}=(val) @#{sym}=val end } end end end
attr_accessorは引数としてシンボルを受け取り、それらの要素に対してfor-each文を繰り返します。
class_eval文で宣言した場所は、RubyではStringとして扱われるようで、そのStringをRubyで評価し実行環境に加えて実行することができるみたいです。(つまり今回は,メソッドをその場で作成するという動的な動作が可能になっています)
でこれを流用して、readerメソッドを作ってみます。
class Module def reader(*syms) syms.each do |sym| class_eval %{ def #{sym} @#{sym} end } end end end class A reader :a def initialize @a = 10 end end a = A.new p a.a
はい、まんまですw
でもこう書いてみるとと動作するのはわかるんだけど(実行できるし)、
なんで実装しているのがModuleクラスなんだろう?
ちなみに、下のソースみたいに使うクラスに移したら実行できませんでした。
class A reader :a def reader(*syms) syms.each do |sym| class_eval %{ def #{sym} @#{sym} end } end end def initialize @a = 10 end end a = A.new p a.a
error はNoMethodErrorですね。readerメソッドを見つけられなかったみたいです。
Moduleクラスに実装しないと動かないみたいです。
でも、そもそもModuleクラスって何?っていう疑問が浮かんできます。(勉強不足ですw)
じゃあModuleクラスの概要を調べてみよう。
というわけでRubyリファレンスを見てみました。
するとプライベートメソッドの中にattrシリーズが実装されていることが分かりました。
そのオーバライドをまつもとさんのソースでは行っていたんだと思います。
でも、なんでclass内部の宣言じゃダメなんだろう。
だったらスーパークラスに実装してあったらいいのかなと思って下のソースを実装してみた。
class A def reader(*syms) syms.each do |sym| class_eval %{ def #{sym} @#{sym} end } end end end class B < A reader :a def initialize @a = 10 end end a = B.new p a.a
どうもだめみたいだ。
どうも気になるので、次回はメタプログラミングじゃなくてModuleクラスについて調べてみようっと。