2012-04-07 5 views
13

tengo este código:Cambie la unión de un Proc en Ruby

l = lambda { a } 
def some_function 
    a = 1 
end 

sólo quiero acceder a por el lambda y un endoscopio especial que se ha definido a ya en algún lugar como en el interior some_function en el ejemplo, o Sólo poco más tarde en el mismo alcance que:

l = lambda { a } 
a = 1 
l.call 

Entonces me encontré al llamar l, todavía está utilizando su propia unión, pero no uno nuevo donde fue llamado.

Y luego traté de utilizarlo como:

l.instance_eval do 
    a = 1 
    call 
end 

Pero esto también fracasó, es extraño que no puedo explicar por qué.

Sé que la solución está usando eval, en el que podría especializar una encuadernación y ejecutar algún código en el texto, pero realmente no quiero usarlo.

Y sé que puede usar una variable global o una variable de instancia. Sin embargo, en realidad mi código está en un entorno incrustado más profundo, por lo que no quiero romper las partes completadas si no es absolutamente necesario.

He referido la clase Proc en la documentación, y he encontrado los nombres de una función binding que hacen referencia al contexto de Proc. Mientras que la función solo proporcionaba una forma de acceder a su enlace pero no puede cambiarlo, excepto al usar Binding#eval. También evalúa el texto, que es exactamente lo que no me gusta hacer.

Ahora la pregunta es, ¿tengo una mejor (o más elegante) forma de implementar esto? O utilizando eval ya es el regular manera?

Editar para responder a @ Andrew:
Bueno, esto es un problema que me encontré cuando estoy escribiendo un analizador léxico, en la que he definido una matriz con número fijo de elementos, hay que incluye al menos un Proc y una expresión regular. Mi propósito es hacer coincidir las expresiones regulares y ejecutar los Procs bajo mi ámbito especial, donde el Proce implicará algunas variables locales que se deben definir más adelante. Y luego encontré el problema anterior.
En realidad, supongo que no es completamente igual a that question, ya que la mía es cómo pasar en vinculante a un Proc en lugar de cómo pasarlo .

@Niklas: Tengo su respuesta, creo que eso es exactamente lo que quiero. Ha solucionado mi problema perfectamente.

+4

Tal vez una buena pregunta para qué se * * ¿quiere hacer esto, o, más bien, ¿qué estás tratando de lograr en última instancia? –

+2

@Andrew: No lo sé. Me interesaría una respuesta a esta pregunta exacta, en lugar de una solución al problema subyacente :) –

+1

@NiklasB. Estoy de acuerdo, pero conocer el objetivo final puede ayudar a comprender mejor el problema. Y estoy bastante seguro de haber respondido una pregunta similar hace un tiempo, y estoy tratando de encontrarlo, pero no puedo ': ('. –

Respuesta

19

Usted puede intentar este truco:

class Proc 
    def call_with_vars(vars, *args) 
    Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self) 
    end 
end 

Para usarse como esto:

irb(main):001:0* lambda { foo }.call_with_vars(:foo => 3) 
=> 3 
irb(main):002:0> lambda { |a| foo + a }.call_with_vars({:foo => 3}, 1) 
=> 4 

Esto no es una solución muy general, sin embargo.Sería mejor si pudiéramos darle Binding ejemplo, en lugar de un hash y haga lo siguiente:

l = lambda { |a| foo + a } 
foo = 3 
l.call_with_binding(binding, 1) # => 4 

utilizando la siguiente, truco más compleja, este comportamiento exacto se puede lograr:

class LookupStack 
    def initialize(bindings = []) 
    @bindings = bindings 
    end 

    def method_missing(m, *args) 
    @bindings.reverse_each do |bind| 
     begin 
     method = eval("method(%s)" % m.inspect, bind) 
     rescue NameError 
     else 
     return method.call(*args) 
     end 
     begin 
     value = eval(m.to_s, bind) 
     return value 
     rescue NameError 
     end 
    end 
    raise NoMethodError 
    end 

    def push_binding(bind) 
    @bindings.push bind 
    end 

    def push_instance(obj) 
    @bindings.push obj.instance_eval { binding } 
    end 

    def push_hash(vars) 
    push_instance Struct.new(*vars.keys).new(*vars.values) 
    end 

    def run_proc(p, *args) 
    instance_exec(*args, &p) 
    end 
end 

class Proc 
    def call_with_binding(bind, *args) 
    LookupStack.new([bind]).run_proc(self, *args) 
    end 
end 

Básicamente nos definimos a nosotros mismos una pila de búsqueda manual de nombres y instance_exec nuestro proceso contra ella. Este es un mecanismo muy flexible. No sólo permite la implementación de call_with_binding, también se puede utilizar para construir mucho más complejas cadenas de búsqueda:

l = lambda { |a| local + func(2) + some_method(1) + var + a } 

local = 1 
def func(x) x end 

class Foo < Struct.new(:add) 
    def some_method(x) x + add end 
end 

stack = LookupStack.new 
stack.push_binding(binding) 
stack.push_instance(Foo.new(2)) 
stack.push_hash(:var => 4) 

p stack.run_proc(l, 5) 

Esto imprime 15, como se esperaba :)

ACTUALIZACIÓN: Código es ahora también disponible at Github. Lo uso para uno de mis proyectos también ahora.

+0

Minit nitpick: quisiste decir 'lambda {| a | local + func + some_method + a} '? Muy buena respuesta! – Brandan

+0

@Brandan: Sí, también me di cuenta de que ahora mismo. Reparado. ¡Gracias! –

+0

@NiklasB. Hola amigo, encontré una solución mejor y más elegante, ¡mira mi código a continuación! –

1
class Proc 
    def call_with_obj(obj, *args) 
     m = nil 
     p = self 
     Object.class_eval do 
      define_method :a_temp_method_name, &p 
      m = instance_method :a_temp_method_name; remove_method :a_temp_method_name 
     end 
     m.bind(obj).call(*args) 
    end 
end 

Y luego usarlo como:

class Foo 
    def bar 
     "bar" 
    end 
end 

p = Proc.new { bar } 

bar = "baz" 

p.call_with_obj(self) # => baz 
p.call_with_obj(Foo.new) # => bar 
+0

¿Cuál es el punto? Esos podrían simplemente escribirse como 'instance_exec (& p)' o 'Foo.new.instance_exec (& p)'. Creo que acabas de reinventar la rueda ... Mi respuesta era mucho más general que eso, pero si 'instance_exec' es suficiente, entonces hazlo :) La aproximación usando un método temporal no es particularmente elegante, porque no es reentrant (como en, no se puede usar de forma anidada, ni se puede usar entre hilos). –

+2

Por lo tanto, en pocas palabras, no considero esto "genial", pero si funciona para usted, es bueno :) –

+0

@NiklasB. Oh, tienes razón. Solía ​​considerar si puedo deshacerme de la evaluación de cadenas. Me enteré de la instancia_exec después de leer su respuesta, pero ... Realmente estoy reinventando la rueda. –

Cuestiones relacionadas