instance_evalとclass_evalについての備忘録
メタプログラミングRubyを読んで書いています。 ちょっとした備忘録です。
instance_evalとclass_evalの挙動の違いについて。
instance_evalとは何か?
instance_evalは、オブジェクトの特異クラスにインスタンスメソッドを定義したり、そのオブジェクト自身が参照できるインスタンス変数を定義、または上書きしたりすることができる。
# instance_eval.rb 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_eval.rb 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. インスタンスには、インスタンス固有のメソッド
# instance_eval.rb 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. インスタンスに定義されたメソッドの行方
# evals.rb 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つ前のコードで、あるインスタンスの中で定義されたメソッドが、他のインスタンスでは使えないという理由がここにあったみたいだ。