1. ホーム
  2. スクリプト・コラム
  3. ルビートピックス

Rubyのinstance_evalメソッドとclass_evalとの比較

2022-01-30 15:59:30

instance_eval メソッド

このBasicObject#instance_evalはJSのbindメソッドと少し似ていますが、bindはこれをオブジェクトに渡すのに対し、instance_evalは指定したオブジェクトにコードブロック(Context Probe)を、一つはオブジェクトに、一つは実行ファイルに渡しています。こうすることで、instance_evalのコードブロックの中で、呼び出し元のオブジェクトの変数にアクセスすることが可能になります。

サンプルコード

class MyClass
  def initialize
    @v = 1
  end
end
obj = MyClass.new

obj.instance_eval do
  self #=> #<MyClass:0x33333 @v=1>
  @v #=> 1 
end

v = 2
obj.instance_eval { @v = v }
obj.instance_eval { @v } # => 2



さらに、instance_eval メソッドには instance_exec メソッドという双子の兄弟がいます。後者は前者よりも柔軟性があり、コードのブロックにパラメータを渡すことができます。

サンプルコード

class C
  def initialize
    @x = 1
  end
end
class D
  def twisted_method
    @y = 2
    #C.new.instance_eval { "@x: #{@x}, @y>: #{y}" }
    C.new.instance_exec(@y) { |y| "@x: #{@x}, @y: #{y}" }
  end
end
# D.new.twisted_method # => "@x: 1, @y: "
D.new.twisted_method # => "@x: 1, @y: 2"


instance_evalの呼び出しによって呼び出し元が現在の自己になったため、クラスCではスコープが入れ替わり、以前のスコープが効かなくなります。この時点で、まだ前の変数@yにアクセスしたい場合は、引数に@yを詰めてinstance_evalと一緒にエスケープする必要がありますが、instance_evalは引数を持てないため、兄弟メソッドのinstance_execを使用するのです。


instance_evalとclass_evalの違いについて
###インスタンスエヴァル
名前から得られる最初の情報は、instance_evalの呼び出し側であるreceiverがインスタンスでなければならないこと、instance_evalブロック内ではselfがreceiverのインスタンス自身であることです。

obj_instance.instance_eval do
 self # => obj_instance
 # current class => obj_instance's singleton class
end
<! --more-->



この定義によれば、あるインスタンスに対して instance_eval が呼ばれた場合、そのインスタンスに対する単相関数 singleton_method をその中で定義することができる。

class A
end

a = A.new
a.instance_eval do
 self # => a
 # current class => a's singleton class
 def method1
  puts 'this is a singleton method of instance a'
 end
end

a.method1
#=> this is a singleton method of instance a

b = A.new
b.method1
#=> NoMethodError: undefined method `method1' for #<A:0x10043ff70>



同様に、Classクラス自体もClassクラスのインスタンスなので、instance_evalもクラスに対して使うことができ、今度はその中にクラスのsingleton_methodを定義する、つまりクラスのクラス関数として定義することができるのです。

つまり、instance_evalを使ってクラス関数クラスメソッドを定義することができるのですが、これはもっと分かりにくいので、解明する必要があります。

class A
end

A.instance_eval do
 self # => A
 # current class => A's singleton class
 def method1
  puts 'this is a singleton method of class A'
 end
end

A.method1
#=> this is a singleton method of class A
class_eval



###クラス評価

改めてclass_evalを見てみると、名前から得られる最初の情報は、class_evalの呼び出し側であるreceiverはクラスでなければならないということ、そしてclass_evalブロックの内部ではselfはreceiverクラス自身であることです。

class A
end

A.class_eval do
 self # => A
 # current class => A
end



この定義によれば、あるクラスに対して class_eval が呼ばれた場合、そのクラスのインスタンス関数 instance_method を定義することができます。

class A
end

a = A.new
a.method1
#=> NoMethodError: undefined method `method1' for #<A:0x10043ff70>

A.class_eval do
 self # => A
 # current class => A
 def method1
  puts 'this is a instance method of class A'
 end
end

a.method1
#=> this is a instance method of class A



つまり、class_evalを使ってインスタンスメソッドを定義することができるのですが、これもより分かりにくいので、把握する必要があります。