クラスがレシーバの場合のclass_eval とinstance_eval の違い

ここ以前から分からなかったので調べてみました。

基本

class_eval
  • Module で定義されている
    • なので、クラスやモジュールにしか使えない
irb(main):009:0> String.respond_to?(:class_eval)
true
irb(main):011:0> Enumerable.respond_to?(:module_eval)
true
irb(main):012:0> 'xxx'.respond_to?(:class_eval)
false
instance_eval
  • Object で定義されている
    • なので、クラス(Class クラスのオブジェクト)にもオブジェクトにも使える
irb(main):003:0> String.respond_to?(:instance_eval)
true
irb(main):004:0> 'a'.respond_to?(:instance_eval)
true

クラスに対しては、class_eval もinstance_eval も同じか?

最初は同じだと思っていたけど、違った。次の例を見ると両者が同じものではないことが分かる。

class Hoge
end

h = Hoge.new

Hoge.class_eval do
  define_method :class_eval_define_method do
    1
  end
end

h.class_eval_define_method # => 1

Hoge.instance_eval do
  define_method :instance_eval_define_method do
    2
  end
end

h.instance_eval_define_method # => 2

Hoge.class_eval do
  def class_eval_def
    3
  end
end

h.class_eval_def # => 3

Hoge.instance_eval do
  def instance_eval_def
    4
  end
end

# クラスメソッドが定義される
h.class.instance_eval_def # => 4
h.instance_eval_def # => 

# ~> -:37: undefined method `instance_eval_def' for #<Hoge:0x24590> (NoMethodError)


define_method の場合、class_eval, instance_eval 共にインスタンスメソッドを定義する。一方、def を使った場合は、class_eval だとインスタンスメソッドを定義するが、instance_eval だとクラスメソッドを定義する。

るりまで両者の定義を見てみる

モジュールのコンテキストで文字列 expr またはモジュール自身をブロック引数とするブロックを評価してその結果を返します。

モジュールのコンテキストで評価するとは、実行中そのモジュールが self になるということです。つまり、そのモジュールの定義式の中にあるかのように実行されます。
....

オブジェクトのコンテキストで文字列 expr またはオブジェクト自身をブロック引数とするブロックを評価してその結果を返します。


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


なんか、メソッド定義の挙動の違いだけ例外的に扱われるみたいに読みとれてしまう。
前半を比較すると、今回の例のようにclass_eval とinstance_eval のレシーバが両方クラス(Hoge)の場合は「class_eval の定義文にあるモジュール」 = 「instance_eval の定義文にあるオブジェクト」=「Hoge」なので、Hoge.class_eval もHoge.instance_eval も同じ挙動をするように感じでしまう。

ここに関してはRHG を見たら理解できた

module_evalではモジュール文やクラス文の内部にいるかのような環境で評価できる。

instance_evalは特異クラス文でselfがそのオブジェクトになった環境で評価できる。


ここの「特異クラス文でselfがそのオブジェクトになった環境」という表現が絶妙!!
つまり、「instance_eval ではself はレシーバのオブジェクトを指すけど、コンテキストはレシーバの特異クラスのコンテキストになる」ってことだった!!


キタ━━━━(゜∀゜)━━━━ッ!!


これなら、define_method とdef に対して両者の挙動が違ったのも理解できる。

  • class_eval の場合
    • define_method はself であるHoge をレシーバとして呼ばれる
    • def でメソッドを定義する時、そのコンテキストはHoge クラスのコンテキストと同じになる
  • instance_eval の場合
    • define_method はself であるHoge をレシーバとして呼ばれる。instance_eval の場合、self はHoge
    • def でメソッドを定義する時、instance_eval の場合、そのコンテキストはHoge クラスの特異クラスのコンテキストと同じになる
      • よって、Hoge クラスのクラスメソッド(Hoge クラスの特異クラスのインスタンスメソッド)を定義する


define_method はメソッドなので、現在のコンテキストではなく、そのレシーバ(self)によって挙動が変わるのがポイント。一方、def はself ではなく、現在のコンテキストに依存する。(実際はコンテキストの構成要素として「self が何なのか」ということも入ると思うので、表現としては適切ではない気がする。多分、コンテキストの内、self 以外の構成要素に関係あるけど、まだそこまで理解してない)
self で評価されないものとして例えば定数とかもこの違いがでる。

irb(main):001:0> class Hoge
irb(main):002:1> X = 1
irb(main):003:1> end
1
irb(main):004:0> class << Hoge
irb(main):005:1> X = 100
irb(main):006:1> end
100
irb(main):007:0> Hoge::X
1
irb(main):008:0> class << Hoge; self; end::X
100
irb(main):009:0> Hoge.class_eval 'X'
1
irb(main):010:0> Hoge.instance_eval 'X'
100

最後にソースも見てみる

  • eval.c(ポイントだけ抜粋)
VALUE
rb_mod_module_eval(argc, argv, mod)
    int argc;
    VALUE *argv;
    VALUE mod;
{
    return specific_eval(argc, argv, mod, mod);
}

VALUE
rb_obj_instance_eval(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    VALUE klass;

    if (SPECIAL_CONST_P(self)) {
	klass = Qnil;
    }
    else {
	klass = rb_singleton_class(self);
    }
    return specific_eval(argc, argv, klass, self);
}

static VALUE
specific_eval(argc, argv, klass, self)
    int argc;
    VALUE *argv;
    VALUE klass, self;
{
    if (rb_block_given_p()) {
	if (argc > 0) {
	    rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc);
	}
	return yield_under(klass, self, Qundef);
...
  • class_eval, instalce_eval 共に、specific_eval を呼ぶ
  • class_eval は「specific_eval(argc, argv, mod, mod);」と、引数の最後の2つは同じものにして呼びだす
  • instance_eval は「specific_eval(argc, argv, klass, self);」と、引数の最後の2つは違うもので呼びだす
  • このklass はself の特異クラスを指す
  • 第3引数はブロックをどのコンテキストで評価するかを指定し、第4引数はブロック内でのself を指定している


おぉぉ!少しスッキリした!まだコンテキストが何によって構成されているかとかまでは理解しきれていませんが、以前より一歩前進!!