2009-05-22 4 views
46
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> 

メソッドの名前にちょうど基づいてFooにクラスメソッドを追加し、Fooにインスタンスメソッドを追加できるようにinstance_evalを追加できるようにしたいと思います。しかし彼らは反対のことをしているようですclass_eval()とinstance_eval()の違いを理解するにはどうすればよいですか?

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

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

ドキュメント:

mod.class_eval(文字列[、ファイル名[、 LINENO]])=>

OBJ MODの 文脈における文字列またはブロックを評価します。これは、クラスにメソッドを追加するために使用することができます 。 instance_evalため

ドキュメント:

{obj.instance_eval | |

OBJブロック} =>は、受信 (OBJ)のコンテキスト内ルビー ソースコード、又は所定のブロック、 を含む文字列を評価します。コンテキストを設定するには、 変数selfがobjに設定され、 コードが実行され、コード にobjのインスタンス変数へのアクセス権が与えられます。

答えて

77

です。したがって、次のコードは同等です。

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は、単にクラスを再開を超える多くの利点を有します。まず変数で簡単に呼び出すことができますが、その意図は明確です。別の利点は、クラスが存在しない場合に失敗することです。したがって、Arrayのスペルが間違っているため、以下の例は失敗します。クラスが単純に再オープンした場合、それは成功するだろう(と新しい間違ったArayクラスが定義されます):

Aray.class_eval do 
    include MyAmazingArrayExtensions 
end 

最後にclass_evalあなたはもう少し極悪非道な何かをやっている場合に便利です文字列を取ることができます一方...

instance_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 

したがって、メソッドは、STRの単一のインスタンスに対して定義されていますing。

Classinstance_evalはなぜクラスメソッドを定義していますか?

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

"This Is Confusing".class 
=> String 

String.class 
=> Class 

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

+4

これは素晴らしい説明です。ありがとう。最後の段落まで、私はあなたに続きました。なぜなら、instance_evalがクラスに対して呼び出されたときにクラスメソッドを定義する理由を説明しようとするところです。最後の行は、私がダイジェストするのが特に難しいです。「クラスメソッドを呼び出すのは間違いです。それは、その特定のClassインスタンスのインスタンスメソッドです。」あなたは、すべてのクラスメソッドがそれ以上のものではないということを意味しますか? –

+0

ええ、ちょっと - それはすべてです。私は明日それをもっと明白にしようとします。 – tomafro

+0

素晴らしい - 私はその問題のために特に新しい質問を追加しました: "なぜinstance_eval()はクラスで呼び出されたときにクラスメソッドを定義するのですか?" http://stackoverflow.com/questions/900704/why-does-instanceeval-define-a-class-method-when-called-on-a-class。 –

5

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

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 
+1

私はFooクラスで呼び出している間、オブジェクトfooに対してinstance_evalを呼び出しています。これがどう関係しているのか分かりません。 –

3

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

はここclass_evalは、モジュールまたはクラスのコンテキスト内の文字列またはブロックを評価し、singleton methodsに関するリンクとsingleton pattern(特定非ルビー)

+0

リンクありがとう! –

16

もう1つ答えは正しいですが、少し深く入ります。

Rubyにはさまざまな種類のスコープがあります。詳細な正式な文書は不足しているようだが、wikipediaによると6つ。この質問に含まれる範囲の種類は、驚くことではないがインスタンスおよびクラスである。

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

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

は、の2つの異なるevalの味はこれらのスコープにどのように影響するかを見てみましょう:どちらの場合も

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です。

+0

+1また、ここではスコープを紹介しています。 – bryantsai

+0

btw、これは1.9未満でしか動作しません。 – bryantsai

+0

すばらしい答え。好奇心のため、最近のルビー版ではこのように動作していないので、Module.nesting [0] 'に代わるものがありますか? – antinome

関連する問題