日々徒然

プログラミングしたりお酒飲んだりする毎日

Rubyのeval三兄弟についてのまとめ(instance_eval, class_eval, module_eval)

はじめに

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_evalclass_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だと文字列とブロックを渡した時の定数参照的な問題がでるっぽい...

だいぶ説明足りてないけど、ひとまずヨシ