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のインスタンス変数へのアクセスを許可します。
ドキュメントに記載されているように、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
を使用すると、メソッドは文字列のその単一インスタンスに対してのみ定義されます。
では、なぜClass
のinstance_eval
がクラスメソッドを定義するのでしょうか。
"This Is Confusing"
と"The Smiths on Charlie's Bus"
が両方ともString
インスタンスであるように、Array
、String
、Hash
、および他のすべてのクラスはそれ自体がインスタンスですClass
の。 #class
を呼び出すことでこれを確認できます。
"This Is Confusing".class
=> String
String.class
=> Class
したがって、instance_eval
を呼び出すと、他のオブジェクトと同じようにクラスで実行されます。 instance_eval
を使用してクラスのメソッドを定義すると、すべてのクラスではなく、クラスのそのインスタンスのみのメソッドが定義されます。そのメソッドをクラスメソッドと呼ぶかもしれませんが、それはその特定のクラスの単なるインスタンスメソッドです。
もう1つの答えは正しいですが、少し詳しく説明します。
Rubyにはさまざまな種類のスコープがあります。 wikipedia によると6つですが、詳細な正式なドキュメントが不足しているようです。この質問に関係するスコープの種類は、当然のことながら、instanceとclass。
現在のインスタンススコープは、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
を使用することです。
私はあなたがそれを間違えたと思います。 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
instance_evalは、問題のオブジェクトインスタンスのシングルトンメソッドを効果的に作成します。 class_evalは、指定されたクラスのコンテキストで通常のメソッドを作成し、そのクラスのすべてのオブジェクトで使用できます。
シングルトンメソッド および シングルトンパターン (Ruby固有ではない)に関するリンクは次のとおりです。
instance_eval
およびclass_eval
を使用すると、コードのチャンクを実行できます。それで、あなたは何を言うかもしれませんか?昔ながらのeval
はこれを行うことができます。ただし、instance_eval
とclass_eval
は、コードのチャンクのブロック引数を受け入れます。したがって、コードのチャンクは文字列である必要はありません。また、instance_eval
とclass_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
に直接入り、したがって、インスタンスメソッドになります。したがって、クラスオブジェクトで呼び出すことはできません。クラスオブジェクトのインスタンスでのみ呼び出すことができます。