メタプログラミングRubyを読んで書いています。
ちょっとした備忘録です。
instance_evalとclass_evalの挙動の違いについて。
instance_evalとは何か?
instance_evalは、オブジェクトの特異クラスにインスタンスメソッドを定義したり、そのオブジェクト自身が参照できるインスタンス変数を定義、または上書きしたりすることができる。class Sample
def initialize
@x = 1
end
end
sample = Sample.new
sample.instance_eval do
@y = 1
def output # => これは特異クラスのインスタンスメソッドになる
@x + @y
end
end
puts sample.output # => 2
puts sample.singleton_class.instance_methods.grep(/output/) # => output
class_evalとは何か?
class_evalは、クラスにインスタンスメソッドやクラスメソッド(特異メソッド)を追加したり、そのクラス自身のインスタンス変数(クラスインスタンス変数)やクラス変数を定義、または上書きしたりするときに利用できる。class Sample
def initialize
@x = 1
end
end
Sample.class_eval do
@y = 1 # クラスインスタンス変数
@@a = 1 # クラス変数
def output # クラス内のインスタンスメソッド
@x
end
end
sample = Sample.new
puts sample.instance_variables # => @x
puts Sample.instance_variables # => @y
puts Sample.class_variables # => @@a
puts sample.output # => 1
puts Sample.instance_methods.grep(/output/) # => output
このように、class_evalの中で、インスタンス変数を定義しようとすると、それはクラスのクラスインスタンス変数になる。
クラス変数はそのまま書けば、クラス変数として扱われる。
メソッドはクラス内のインスタンスメソッドとして定義されるので、もちろん、作成したオブジェクト(sample)はそのメソッドを利用することができる。
とりあえず理解してけば良いこと
instance_evalについて
・レシーバーであるインスタンスのインスタンス変数を定義したり、上書きすることができる。
・レシーバーであるインスタンスが利用できるメソッド(インスタンスの特異クラスのインスタンスメソッド。他のインスタンスは当然ながら、このメソッドを利用することはできない)を定義したり、上書きすることができる。
class_evalについて
・レシーバーであるクラスのインスタンス変数(クラスインスタンス変数)やクラス変数を定義したり、上書きすることができる。
・レシーバーであるクラスのインスタンスメソッドを定義したり、上書きすることができる。
これだけわかっておけば、基本問題ない気がする。
試してみたこと
1. インスタンスには、インスタンス固有のメソッド
class MyClass
def initialize
@x = 1
end
end
first = MyClass.new
first.instance_eval do
@y = 1
def calc
@x + @y
end
end
puts first.calc
second = MyClass.new
puts second.calc # => Error secondにはcalcメソッドが定義されていないから。
instance_evalのレシーバーであるオブジェクトだけが、定義や上書きの対象になる様子。
上のコードではオブジェクトfirstにinstance_evalをして、中身をいじくっているが、
そのあとに定義されたオブジェクトsecondには全く反映されていない。
2. インスタンスに定義されたメソッドの行方
class InstanceEval
def initialize
@x = 1
end
end
sample = InstanceEval.new
sample.instance_eval do
def output
@x
end
end
puts sample.instance_variables # => @x
puts sample.output # => 1
class ClassEval
def initialize
@y = 1
end
end
ClassEval.class_eval do
@v = 1 # クラスインスタンス変数。クラスのインスタンス変数。
@@a = 1 # クラス変数。initializeの外に書いても、クラス変数。
def output
@y
end
end
another_sample = ClassEval.new
puts sample.singleton_methods.grep(/output/) # => output
puts sample.singleton_class.instance_methods.grep(/output/) # => output
puts InstanceEval.instance_methods.grep(/output/)
puts ClassEval.instance_methods.grep(/output/) # => output
puts ClassEval.instance_variables # => @v
puts ClassEval.class_variables # => @@a
puts another_sample.instance_variables # => @y
instance_evalで定義したメソッドは、instance_methodsメソッドで発見できなかった。
なので、singleton_classのインスタンスメソッドを探したところ、発見した。
instance_evalで定義したメソッドは、レシーバーの特異クラスのインスタンスメソッドになるみたい。
1つ前のコードで、あるインスタンスの中で定義されたメソッドが、他のインスタンスでは使えないという理由がここにあったみたいだ。