読者です 読者をやめる 読者になる 読者になる

instance_evalとclass_eval

今日たまたまenum_helpというgemの実装を見てて、「instance_evalとclass_evalってどっちがどうなんだっけ」ってなったので少し調べた。

試しにenum_helpと似たようなサンプルを実装してみたので、サンプルとるりまの説明文をもとに備忘録がてら書いていく。

環境

調べた結果

サンプル

class Foo; end
module Bar
  def self.instance_define(klass, method_name)
    klass.class_eval <<-METHOD, __FILE__, __LINE__
    def #{method_name}
      puts "#{method_name} is instance method."
    end
    METHOD
  end

  def self.class_define(klass, method_name)
    klass.instance_eval <<-METHOD, __FILE__, __LINE__
    def #{method_name}
      puts "#{method_name} is class method."
    end
    METHOD
  end
end

Bar.instance_define(Foo, 'baz')
Bar.class_define(Foo, 'baz')
Foo.new.baz
#=> baz is instance method.
Foo.baz
#=> baz is class method.

class_eval

るりまの説明を見るとclass_evalは以下のような説明がなされている。

モジュールのコンテキストで文字列 expr またはモジュール自身をブロックパラメータとするブロックを 評価してその結果を返します。
モジュールのコンテキストで評価するとは、実行中そのモジュールが self になるということです。 つまり、そのモジュールの定義式の中にあるかのように実行されます。

サンプルの実装に即していえば、klass(すなわちFoo)のコンテキストでbazメソッド定義が評価されたということになる。

つまり、BarクラスからFooクラスへとbazメソッド評価コンテキストを移動させたのがclass_evalだといえる。

だから上のサンプルは

class Foo
  def baz
    puts "baz is instance method."
  end
end

と定義したに等しいということになる。

instance_eval

一方instance_evalの説明は以下。

オブジェクトのコンテキストで文字列 expr またはオブジェクト自身をブロックパラメータとするブロックを 評価してその結果を返します。
オブジェクトのコンテキストで評価するとは評価中の self をそのオブジェクトにして実行するということです。 また、文字列 expr やブロック中でメソッドを定義すればそのオブジェクトの特異メソッドが定義されます。

サンプルでいえばklass(すなわちFoo)がこのるりまの説明でいうselfである。

そのselfをオブジェクトとしてオブジェクトの特異メソッドを定義するということは、 以下のようにFooというクラスインスタンスの特異メソッド、すなわちクラスメソッドが定義されたことになる。

class Foo
  def self.baz
    puts "baz is class method."
  end
end

所感

以前メタプログラミングRubyでこの辺りを学んだ気がするが、すっかり記憶から抜け落ちていた。 また読み直そうと思う。

参考