はじめに
RubyGoldのお勉強でeval三兄弟について調べたのでまとめた
instance_eval
オブジェクトに対する操作
インスタンスに対してinstance_eval
すると、そのインスタンスの特異メソッドを作成してくれる
例
class Piyo def cry p "piyo" end end piyo = Piyo.new piyo.cry #=> "piyo" piyo.instance_eval do def cry p "piyo" * 2 end end piyo.cry #=> "piyopiyo"
self
に対してcry
メソッドを定義(再定義)した結果、piyopiyoが出力された
ちなみに文字列とブロックを渡す場合で挙動が変わる
ブロックを渡した場合はそのブロックの外側に対する定義、文字列を渡した場合はselfに対する定義になる
こちらも例
class A def const p CONST end end a = A.new # ブロックの外側のObjectにCONSTが定義される a.instance_eval do CONST = "Object#A" end # selfであるインスタンスaにCONSTが定義される a.instance_eval <<-EOS CONST = "eval#A" EOS a.const #=> "Object#A" class << a def singleton_const p CONST end end a.singleton_const #=> "eval#A"
class_eval
ブロックor文字列を渡すと、渡した物をそのクラス内で定義してくれる
class A end A.class_eval do def call p "A" end end A.new.call #=> "A"
こいつもブロックor文字列で動きが変わる
ブロックが渡された場合は定数とクラス変数のスコープがブロックの外側、文字列が渡された時はselfの定義内になる
例
class A def const p CONST p Object::CONST end end # ブロックの外側のObjectのコンテキストでCONSTが定義される A.class_eval do CONST = "block#A" end # selfであるAのコンテキスト内でCONSTが定義される A.class_eval <<-EOS CONST = "string#A" EOS A.new.const #=> "string#A" #=> "block#A"
つまりinstance_eval
もclass_eval
も
ブロックが渡された場合はブロックの外側、文字列が渡されたらselfのコンテキスト内に対しての操作になる
module_eval
class_eval
と一緒
三兄弟(双子と一人)
え、instance_evalにもクラス渡せるよね?
instance_eval
はBasicObjectのメソッドのため、クラスをレシーバーにしても実行できる
わざわざクラスに対してinstance_eval
することも無いとは思うけど気になったので調べてみた
class A end # クラスに対してinstance_eval A.instance_eval do # selfはAなのでAに対する特異メソッド == Aのクラスメソッドが定義される def call p "A" end # selfはAなのでAに対するインスタンスメソッドが定義される define_method :define_call do p "define#call" end end A.call => "A" A.new.define_call => "define#call"
class B end b = B.new # Bクラスのインスタンスbに対してinstance_eval b.instance_eval do # selfはbなのでbに対する特異メソッドが定義される def call p "B" end # selfはbなのでdefine_methodが呼び出せない、エラーになる # define_singleton_methodを呼べば、bの特異メソッドとして定義可能 define_method :define_call do p "define#call" end end
つまりinstance_eval
はselfに対するメソッド定義、class_eval
はselfのコンテキスト内でメソッド定義してくれている
まとめ
RubyGoldだと文字列とブロックを渡した時の定数参照的な問題がでるっぽい...
だいぶ説明足りてないけど、ひとまずヨシ