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クラスについて調べてみようっと。