2009-05-22 10 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> 

Simplemente en función del nombre de los métodos, Esperaría que class_eval le permita agregar un método de clase a Foo y instance_eval para permitirle agregar un método de instancia a Foo. Pero parecen hacer lo opuesto.¿Cómo entender la diferencia entre class_eval() y instance_eval()?

En el ejemplo anterior si llama a class_bar en la clase Foo obtiene un error de método indefinido y si llama a instance_bar en la instancia devuelta por Foo.new, también obtiene un error de método indefinido. Ambos errores parecen contradecir una comprensión intuitiva de lo que class_eval y instance_eval deberían hacer.

¿Cuál es la diferencia entre estos métodos?

Documentación para class_eval:

mod.class_eval (string [, nombre de archivo [, lineno]]) => obj

Evalúa la cadena o bloque en el contexto de mod . Esto se puede usar para agregar métodos a una clase.

Documentación para instance_eval:

obj.instance_eval {| | bloque} => obj

evalúa una cadena que contiene de rubíes código fuente, o el bloque dado, en el contexto del receptor (obj). Para establecer el contexto, , la variable self se establece en obj, mientras que se está ejecutando el código, dando al código acceso a las variables de instancia de obj.

Respuesta

77

Como dice la documentación, class_eval evalúa la cadena o bloque en el contexto del módulo o clase. Por lo que las siguientes piezas de código son equivalentes:

class String 
    def lowercase 
    self.downcase 
    end 
end 

String.class_eval do 
    def lowercase 
    self.downcase 
    end 
end 

En cada caso, la clase String se ha vuelto a abrir y un nuevo método definido. Ese método está disponible en todas las instancias de la clase, por lo que:

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

class_eval tiene una serie de ventajas sobre simplemente la reapertura de la clase.En primer lugar, puede llamarlo fácilmente a una variable, y está claro cuál es su intención. Otra ventaja es que fallará si la clase no existe. Por lo tanto, el siguiente ejemplo fallará ya que Array se escribe incorrectamente. Si la clase se volvió a abrir, simplemente, que tendría éxito (y se define una nueva clase incorrecta Aray):

Aray.class_eval do 
    include MyAmazingArrayExtensions 
end 

Finalmente class_eval puede tomar una cadena, que puede ser útil si está haciendo algo un poco más nefasto ...

instance_eval por el contrario evalúa código contra una sola instancia de objeto:

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 

Así que con instance_eval, el método sólo se define por esa sola instancia de un str En g.

Entonces, ¿por qué instance_eval en un Class define los métodos de clase?

Así como "This Is Confusing" y "The Smiths on Charlie's Bus" son ambos casos String, Array, String, Hash y todas las demás clases son a su vez los casos de Class. Puede comprobar esto llamando #class en ellos:

"This Is Confusing".class 
=> String 

String.class 
=> Class 

Así que cuando llamamos instance_eval hace lo mismo en una clase como lo haría en cualquier otro objeto. Si usamos instance_eval para definir un método en una clase, definirá un método solo para esa instancia de clase, no para todas las clases. Podríamos llamar a ese método un método de clase, pero es solo un método de instancia para esa clase en particular.

+4

Esta es una excelente explicación. Gracias. Te seguí hasta el último párrafo donde intentas explicar por qué instance_eval define los métodos de clase cuando se llama a una clase. La última línea es particularmente difícil de digerir: "Llamar a un método de clase es un error, es solo un método de instancia en esa instancia de Clase particular". ¿Quiere decir que todos los métodos de clase no son más que eso? –

+0

Sí, exactamente, eso es todo lo que son. Trataré de hacerlo más claro mañana. – tomafro

+0

Fantástico: acabo de agregar una nueva pregunta específicamente para ese tema: "¿Por qué instance_eval() define un método de clase cuando se llama a una clase?" http://stackoverflow.com/questions/900704/why-does-instanceeval-define-a-class-method-when-called-on-a-class. –

5

Creo que lo tienes mal. class_eval agrega el método en la clase, por lo que todas las instancias tendrán el método. instance_eval agregará el método solo a un objeto específico.

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

Llamas a instance_eval en el objeto foo mientras lo estoy llamando en la clase Foo. No veo cómo se relaciona esto. –

3

instance_eval crea efectivamente un método singleton para la instancia del objeto en cuestión. class_eval creará un método normal en el contexto de la clase dada, disponible para todos los objetos de esa clase.

Aquí hay un enlace con respecto singleton methods y la singleton pattern (no específica rubí)

+0

¡Gracias por los enlaces! –

16

La otra respuesta es correcta, pero permítanme profundizar un poco.

Ruby tiene diferentes tipos de alcance; seis de acuerdo con wikipedia, aunque parece que falta documentación formal detallada. Los tipos de alcance involucrados en esta pregunta son, como es lógico, instancia y clase.

El alcance actual de la instancia está definido por el valor de self. Todas las llamadas a métodos no calificadas se envían a la instancia actual, como cualquier referencia a variables de instancia (que se parecen a @this). Sin embargo, def no es una llamada a método. El objetivo para los métodos creados por def es la clase (o módulo) actual, que se puede encontrar con Module.nesting[0].

Vamos a ver cómo los dos sabores diferentes eval afectan estos ámbitos:

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

En ambos casos, el alcance ejemplo es el objeto sobre el que se llama * _eval.

Para class_eval, el alcance de clase también se convierte en el objeto de destino, por lo que def crea métodos de instancia para esa clase/módulo.

Para instance_eval, el alcance de clase se convierte en singleton class (también conocido como metaclase, clase de usuario) del objeto de destino.Los métodos de instancia creados en la clase singleton para un objeto se convierten en métodos singleton para ese objeto. Los métodos de Singleton para una clase o módulo son los comúnmente llamados métodos de clase .

El alcance de clase también se utiliza para resolver constantes. Las variables de clase (@@these @@things) se resuelven con el alcance de clase, pero se saltan las clases individuales al buscar en la cadena de anidamiento del módulo. La única forma que he encontrado para acceder a las variables de clase en clases singleton es con class_variable_get/set.

+0

+1 para también introducir el alcance aquí, además de solo por otras respuestas. – bryantsai

+0

por cierto, esto solo funciona en 1.9. – bryantsai

+0

Gran respuesta. Por curiosidad, ¿hay algún sustituto para 'Module.nesting [0]' ya que no parece funcionar de esta manera en las versiones recientes de ruby? – antinome

Cuestiones relacionadas