2010-03-22 18 views
9

Estoy redefiniendo un método en un objeto en ruby ​​y necesito que el nuevo método sea un cierre. Por ejemplo:Definir un método que sea un cierre en Ruby

def mess_it_up(o) 
    x = "blah blah" 

    def o.to_s 
    puts x # Wrong! x doesn't exists here, a method is not a closure 
    end 
end 

Ahora bien, si defino una Proc, es un cierre:

def mess_it_up(o) 
    x = "blah blah" 

    xp = Proc.new {|| 
    puts x # This works 
    end 

    # but how do I set it to o.to_s. 

    def o.to_s 
    xp.call # same problem as before 
    end 
end 

Alguna idea de cómo lo hacen?

Gracias.

Respuesta

23

Esto funciona (probado en IRB):

NOTA: Esto cambia sólo str - no todos los casos de cadena. Lea a continuación para obtener más información sobre por qué esto funciona

another_str = "please don't change me!" 
str =   "ha, try to change my to_s! hahaha!" 
proc = Proc.new { "take that, Mr. str!" } 

singleton_class = class << str; self; end 

singleton_class.send(:define_method, :to_s) do 
    proc.call 
end 

puts str.to_s   #=> "take that, Mr. str!" 
puts another_str.to_s #=> "please don't change me!" 

# What! We called String#define_method, right? 

puts String   #=> String 
puts singleton_class #=> #<Class:#<String:0x3c788a0>> 

# ... nope! singleton_class is *not* String 
# Keep reading if you're curious :) 

Esto funciona porque se está abriendo de singleton class str y la definición de un método allí. Debido a que esto, así como la llamada a Module#define_method, tiene lo que algunos llaman un "alcance plano", puede acceder a variables que estarían fuera del alcance si usó def to_s; 'whatever'; end.

Es posible que desee echa un vistazo a algunos de estos otros "hechizos" metaprogramación aquí:

media.pragprog.com/titles/ppmetr/spells.pdf


¿Por qué sólo cambian str?

Porque Ruby tiene un par de trucos interesantes en sus mangas. En el modelo de objetos de Ruby, una invocación a un método da como resultado que el receptor no solo busque su clase (y son antepasados ​​), sino también su clase singleton (o como lo llamaría Matz, es clase propia). Esta clase singleton es lo que le permite [re] definir un método para un solo objeto. Estos métodos se llaman "métodos singleton". En el ejemplo anterior, estamos haciendo exactamente eso: definir un nombre de método singleton to_s. Es functionaly idéntica a ésta:

def str.to_s 
    ... 
end 

La única diferencia es que se llega a utilizar un cierre al llamar Module#define_method, mientras que def es una palabra clave, lo que resulta en un cambio de alcance.

¿Por qué no puede ser más sencillo?

Bueno, la buena noticia es que se está programando en Ruby, por lo que no dude en ir loco:

class Object 
    def define_method(name, &block) 
    singleton = class << self; self; end 
    singleton.send(:define_method, name) { |*args| block.call(*args) } 
    end 
end 


str = 'test' 
str.define_method(:to_s) { "hello" } 
str.define_method(:bark) { "woof!" } 
str.define_method(:yell) { "AAAH!" } 

puts str.to_s #=> hello 
puts str.bark #=> woof! 
puts str.yell #=> AAAH! 

Y, si usted es curioso ...

Usted conocer los métodos de clase? O, en algunos idiomas, los llamaríamos métodos estáticos? Bueno, esos no realmente existen en Ruby. En Ruby, los métodos de clase son solo métodos definidos en la clase singleton del objeto Class.

Si todo eso suena loco, eche un vistazo a los enlaces que proporcioné arriba. Una gran parte del poder de Ruby solo se puede aprovechar si sabes cómo metaprogramar, en cuyo caso querrás saber realmente sobre las clases/métodos únicos y, más en general, el modelo de objetos de Ruby.

HTH

-Charles

+0

Pero necesito modificarlo solo en un objeto, no en todos (en la clase). – Pablo

+0

Eso es lo que hace. Cada objeto tiene lo que se llama una clase singleton: su propia clase personal para almacenar métodos. Pruébelo: verá que otras instancias de String no se ven afectadas. Actualizaré mi respuesta con una prueba para mostrar que funciona. – Charles

+0

Oh, mi error, lo intentaré. – Pablo

0

Esto parece funcionar.

class Foo 
    def mess_it_up(o) 
    x = "blah blah" 

    o.instance_variable_set :@to_s_proc, Proc.new { puts x } 
    def o.to_s 
     @to_s_proc.call 
    end 
    end 
end 

var = Object.new 
Foo.new.mess_it_up(var) 

var.to_s 

El problema es que el código en def no se evalúa hasta que se ejecuta, y en un nuevo ámbito de aplicación. Por lo tanto, primero debe guardar el bloque en una variable de instancia en el objeto y luego retomarlo.

Y define_method no funciona porque es un método de clase, lo que significa que debería llamarlo en la clase de su objeto, dando ese código a TODAS las instancias de esa clase, y no solo a esta instancia.

+0

No, tendrá que utilizar 'Módulo # define_method' en la clase singleton del objeto, en cuyo caso SÓLO ese objeto se cambia. Ejecute el ejemplo en mi respuesta si usted duda de mí :) – Charles

+1

Sí, su respuesta es mejor. Me olvidé de la clase singleton. –

9

Feature #1082 implementada en Ruby 1.9.2 hace que esta es una tarea fácil con Object#define_singleton_method:

def mess_it_up(o) 
    x = "blah blah" 

    # Use Object#define_singleton_method to redefine `to_s' 
    o.define_singleton_method(:to_s) { x } 
end 

Los conceptos involucrados siguen siendo los mismos que en mi previous answer, que proporciona una descripción más detallada de cómo funciona esto en el modelo de objetos de Ruby, así como una definición Object#define_method que es conceptualmente la misma que la de Ruby 1.9.2 Object#define_singleton_method.

Otros métodos que pueden resultar útiles para tareas similares:

+0

Tuve que usar esto, cuando quería para crear un método de instancia que personalice un objeto agregando llamadas a los métodos de la instancia de creación: 'clase A; def foo (b); pone "fooing # {b}"; fin; def agument (b); b.define_singleton_class (: foo) {foo (self); }; fin; fin'. Obviamente, esto no funciona, porque no hay una forma mágica de hacer referencia al "yo" que estaba en el alcance del cierre. La solución fue sacar una página del libro de Javascript y hacer algo extraño como: 'myself = self' y luego usar' myself' en el método singleton para llamar al padre. ¿Suena razonable? – Guss

Cuestiones relacionadas