web-dev-qa-db-ja.com

class_eval()とinstance_eval()の違いを理解するにはどうすればよいですか?

Foo = Class.new
Foo.class_eval do
  def class_bar
    "class_bar"
  end
end
Foo.instance_eval do
  def instance_bar
    "instance_bar"
  end
end
Foo.class_bar       #=> undefined method ‘class_bar’ for Foo:Class
Foo.new.class_bar   #=> "class_bar"
Foo.instance_bar       #=> "instance_bar"
Foo.new.instance_bar   #=> undefined method ‘instance_bar’ for #<Foo:0x7dce8>

メソッドの名前だけに基づいて、class_evalを使用するとFooにクラスメソッドを追加でき、instance_evalを使用するとFooにインスタンスメソッドを追加できると思います。しかし、彼らは反対のことをしているようです

上記の例では、Fooクラスでclass_barを呼び出すと、未定義のメソッドエラーが発生し、Foo.newによって返されたインスタンスでinstance_barを呼び出すと、未定義のメソッドエラーも発生します。どちらのエラーも、class_evalとinstance_evalが何をすべきかについての直感的な理解と矛盾しているようです。

これらの方法の実際の違いは何ですか?

class_eval のドキュメント:

mod.class_eval(string [、filename [、lineno]])=> obj

modのコンテキストで文字列またはブロックを評価します。これは、クラスにメソッドを追加するために使用できます。

instance_eval のドキュメント:

obj.instance_eval {| |ブロック} => obj

レシーバー(obj)のコンテキスト内で、Rubyソースコードまたは指定されたブロックを含む文字列を評価します。コンテキストを設定するために、変数selfはコードの実行中にobjに設定し、コードにobjのインスタンス変数へのアクセスを許可します。

52
pez_dispenser

ドキュメントに記載されているように、class_evalはモジュールまたはクラスのコンテキストで文字列またはブロックを評価します。したがって、次のコードは同等です。

class String
  def lowercase
    self.downcase
  end
end

String.class_eval do
  def lowercase
    self.downcase
  end
end

いずれの場合も、Stringクラスが再度開かれ、新しいメソッドが定義されています。このメソッドは、クラスのすべてのインスタンスで使用できるため、次のようになります。

"This Is Confusing".lowercase 
=> "this is confusing"
"The Smiths on Charlie's Bus".lowercase
=> "the smiths on charlie's bus"

class_evalには、単にクラスを再度開くよりも多くの利点があります。まず、変数で簡単に呼び出すことができ、意図が明確です。もう1つの利点は、クラスが存在しない場合に失敗することです。したがって、Arrayのスペルが間違っているため、以下の例は失敗します。クラスが単に再開された場合、成功します(そして、新しい誤ったArayクラスが定義されます):

Aray.class_eval do
  include MyAmazingArrayExtensions
end

最後に、class_evalは文字列を取ることができます。これは、もう少し悪質なことをしている場合に役立ちます...

一方、instance_evalは、単一のオブジェクトインスタンスに対してコードを評価します。

confusing = "This Is Confusing"
confusing.instance_eval do
  def lowercase
    self.downcase
  end
end   

confusing.lowercase
=> "this is confusing"
"The Smiths on Charlie's Bus".lowercase
NoMethodError: undefined method ‘lowercase’ for "The Smiths on Charlie's Bus":String

したがって、instance_evalを使用すると、メソッドは文字列のその単一インスタンスに対してのみ定義されます。

では、なぜClassinstance_evalがクラスメソッドを定義するのでしょうか。

"This Is Confusing""The Smiths on Charlie's Bus"が両方ともStringインスタンスであるように、ArrayStringHash、および他のすべてのクラスはそれ自体がインスタンスですClassの。 #classを呼び出すことでこれを確認できます。

"This Is Confusing".class
=> String

String.class
=> Class

したがって、instance_evalを呼び出すと、他のオブジェクトと同じようにクラスで実行されます。 instance_evalを使用してクラスのメソッドを定義すると、すべてのクラスではなく、クラスのそのインスタンスのみのメソッドが定義されます。そのメソッドをクラスメソッドと呼ぶかもしれませんが、それはその特定のクラスの単なるインスタンスメソッドです。

88
tomafro

もう1つの答えは正しいですが、少し詳しく説明します。

Rubyにはさまざまな種類のスコープがあります。 wikipedia によると6つですが、詳細な正式なドキュメントが不足しているようです。この質問に関係するスコープの種類は、当然のことながら、instanceclass

現在のインスタンススコープは、selfの値によって定義されます。修飾されていないすべてのメソッド呼び出しは、インスタンス変数(@thisのように見えます)への参照と同様に、現在のインスタンスにディスパッチされます。

ただし、defはメソッド呼び出しではありません。 defによって作成されたメソッドのターゲットは、現在のクラス(またはモジュール)であり、Module.nesting[0]で見つけることができます。

2つの異なる評価フレーバーがこれらのスコープにどのように影響するかを見てみましょう。

String.class_eval { [self, Module.nesting[0]] } => [String, String] String.instance_eval { [self, Module.nesting[0]] } => [String, #<Class:String>]

どちらの場合も、インスタンススコープは* _evalが呼び出されるオブジェクトです。

class_evalの場合、クラススコープもターゲットオブジェクトになるため、defはそのクラス/モジュールのインスタンスメソッドを作成します。

instance_evalの場合、クラススコープはターゲットオブジェクトのシングルトンクラス(別名メタクラス、固有クラス)になります。オブジェクトのシングルトンクラスで作成されたインスタンスメソッドは、そのオブジェクトのシングルトンメソッドになります。クラスまたはモジュールのシングルトンメソッドは、一般的に(そしてやや不正確に)クラスメソッドと呼ばれるものです。

クラススコープは、定数を解決するためにも使用されます。クラス変数(@@these @@things)はクラススコープで解決されますが、モジュールのネストチェーンを検索するときにシングルトンクラスをスキップします。シングルトンクラスのクラス変数にアクセスするために私が見つけた唯一の方法は、class_variable_get/setを使用することです。

17
jedediah

私はあなたがそれを間違えたと思います。 class_evalはクラスにメソッドを追加するため、すべてのインスタンスにメソッドが含まれます。 instance_evalは、メソッドを1つの特定のオブジェクトにのみ追加します。

foo = Foo.new
foo.instance_eval do
  def instance_bar
    "instance_bar"
  end
end

foo.instance_bar      #=> "instance_bar"
baz = Foo.new
baz.instance_bar      #=> undefined method
5
besen

instance_evalは、問題のオブジェクトインスタンスのシングルトンメソッドを効果的に作成します。 class_evalは、指定されたクラスのコンテキストで通常のメソッドを作成し、そのクラスのすべてのオブジェクトで使用できます。

シングルトンメソッド および シングルトンパターン (Ruby固有ではない)に関するリンクは次のとおりです。

3
jess

instance_evalおよびclass_evalを使用すると、コードのチャンクを実行できます。それで、あなたは何を言うかもしれませんか?昔ながらのevalはこれを行うことができます。ただし、instance_evalclass_evalは、コードのチャンクのブロック引数を受け入れます。したがって、コードのチャンクは文字列である必要はありません。また、instance_evalclass_evalは受信者を許可します(古いevalとは異なります)。したがって、クラスオブジェクトまたはインスタンスオブジェクトでさえ、これら2つの最新のメソッドを呼び出すことができます。

class A
end

A.instance_eval do
  # self refers to the A class object
  self
end

a = A.new

a.instance_eval do
  # self refers to the a instance object
  self
end

また、Rubyレシーバーなしでメソッドを呼び出すと、メソッドはselfで呼び出されます。これは、instance_evalブロックで呼び出したオブジェクトです。 instance_eval on。インスタンス変数はRubyではプライベートです。定義されているクラスの外部ではアクセスできません。ただし、インスタンス変数はselfに格納されているため、instance_evalでアクセスできます。 (同じことが、レシーバーで呼び出すことができないプライベートメソッドにも当てはまります):

class A
  def initialzie
    @a = “a”
  end

  private

  def private_a
    puts “private a”
  end
end

a = A.new
puts a.instance_eval {  @a }
# => “a”
puts a.instance_eval {  private_a }
# => “private a”

instance_evalおよびclass_evalのレシーバーにメソッドを追加することもできます。ここでそれをinstance_evalに追加します。

class A
end

A.instance_eval do
  def a_method
    puts “a method”
  end
end

A.a_method
# =>  a method

さて、ちょっと考えてみてください。 instance_evalを使用し、そのblockでメソッドを定義してから、クラスオブジェクト自体でメソッドを呼び出しました。それはクラスメソッドではありませんか?必要に応じて、これを「クラス」メソッドと考えてください。しかし、私たちが行ったのはinstance_evalブロックのレシーバーでメソッドを定義することだけで、レシーバーはたまたまAでした。インスタンスオブジェクトでも同じことが簡単にできます。

 a.instance_eval do
  def a_method
    puts "a method"
  end
end

a.a_method
# => a method

そして、それはまったく同じように機能します。クラスメソッドを他の言語のクラスメソッドとは考えないでください。これらは、selfがクラスオブジェクトである場合(Class.newのようにclass A endから拡張)、selfで定義されたメソッドにすぎません。

しかし、私はこの答えを受け入れられた答えよりも少し深く理解したいと思います。 instance_evalは、実際に配置したメソッドをどこに貼り付けますか?それらはレシーバーのsingletonクラスに入ります!レシーバーでinstance_evalを呼び出すとすぐに、Rubyインタープリターはsingleton_classを開き、ブロックで定義されたメソッドをこのsingleton_class内に配置します。クラスでextendを使用するのと同じです(extendはシングルトンクラスを開き、渡されたモジュール内のメソッドをシングルトンクラスに拡張するために配置するため)!これは、一部であるsingleton_classを開きます。継承階層の(親クラスの直前):A -> singleton_class -> Parent

では、class_evalの違いは何ですか? class_evalは、クラスとモジュールでのみ呼び出すことができます。 selfは引き続きレシーバーを参照します:

class A
end

A.class_eval do
  # self is A
  self
end

ただし、instance_evalとは異なり、class_evalブロックでメソッドを定義すると、クラスオブジェクト自体ではなく、クラスのインスタンスで使用できるようになります。 class_evalを使用すると、メソッドは継承階層のシングルトンクラスに追加されません。代わりに、メソッドがレシーバーのcurrent classに追加されます!したがって、class_evalでメソッドを定義すると、そのメソッドはcurrent classに直接入り、したがって、インスタンスメソッドになります。したがって、クラスオブジェクトで呼び出すことはできません。クラスオブジェクトのインスタンスでのみ呼び出すことができます。

1
Donato