2008-10-14 10 views
16

Quiero poder escribir un lambda/Proc en mi código Ruby, serializarlo para que pueda escribirlo en el disco, y luego ejecutar el lambda más tarde. Algo así como ...¿Cómo se escribe/serializa el código de Ruby?

x = 40 
f = lambda { |y| x + y } 
save_for_later(f) 

Más tarde, en una ejecución separada del intérprete Ruby, quiero ser capaz de decir ...

f = load_from_before 
z = f.call(2) 
z.should == 42 

Marshal.dump no funciona para Proc. Sé que Perl tiene Data::Dump::Streamer, y en Lisp esto es trivial. ¿Pero hay una forma de hacerlo en Ruby? En otras palabras, ¿cuál sería la implementación de save_ para _ posterior?

Editar: My answer below es agradable, pero no se cierra sobre variables libres (como x) y serializarlos junto con la lambda. Así que en mi ejemplo ...

x = 40 
s = save_for_later { |y| x + y } 
# => "lambda { |y|\n (x + y)\n}" 

... la cadena de salida no incluye una definición para x. ¿Hay alguna solución que tenga esto en cuenta, tal vez mediante la serialización de la tabla de símbolos? ¿Puedes acceder a eso en Ruby?

Editar 2: Actualicé mi respuesta para incorporar serializar variables locales. Esto parece aceptable.

Respuesta

11

Uso Ruby2Ruby

def save_for_later(&block) 
    return nil unless block_given? 

    c = Class.new 
    c.class_eval do 
    define_method :serializable, &block 
    end 
    s = Ruby2Ruby.translate(c, :serializable) 
    s.sub(/^def \S+\(([^\)]*)\)/, 'lambda { |\1|').sub(/end$/, '}') 
end 

x = 40 
s = save_for_later { |y| x + y } 
# => "lambda { |y|\n (x + y)\n}" 
g = eval(s) 
# => #<Proc:[email protected](eval):1> 
g.call(2) 
# => 42 

Esto es muy bueno, pero no se cierra sobre variables libres (como x) y serializarlos junto con la lambda.

Para serialize variables también, puede iterar sobre local_variables y serializarlos también. El problema, sin embargo, es que local_variables desde save_for_later tiene acceso solo a c y s en el código anterior, es decir, variables locales al código de serialización, no al llamador. Desafortunadamente, debemos impulsar el agarre de las variables locales y sus valores a la persona que llama.

Quizás esto sea algo bueno, porque, en general, encontrar todas las variables libres en una pieza de código de Ruby es undecidable. Además, idealmente también ahorraríamos global_variables y cualquier clase cargada y sus métodos reemplazados. Esto parece poco práctico.

El uso de este enfoque simple, se obtiene lo siguiente:

def save_for_later(local_vars, &block) 
    return nil unless block_given? 

    c = Class.new 
    c.class_eval do 
    define_method :serializable, &block 
    end 
    s = Ruby2Ruby.translate(c, :serializable) 
    locals = local_vars.map { |var,val| "#{var} = #{val.inspect}; " }.join 
    s.sub(/^def \S+\(([^\)]*)\)/, 'lambda { |\1| ' + locals).sub(/end$/, '}') 
end 

x = 40 
s = save_for_later(local_variables.map{ |v| [v,eval(v)] }) { |y| x + y } 
# => "lambda { |y| _ = 40; x = 40;\n (x + y)\n}" 

# In a separate run of Ruby, where x is not defined... 
g = eval("lambda { |y| _ = 40; x = 40;\n (x + y)\n}") 
# => #<Proc:[email protected](eval):1> 
g.call(2) 
# => 42 

# Changing x does not affect it. 
x = 7 
g.call(3) 
# => 43 
+1

Mi versión de Ruby2Ruby (1.2.4) no parecen tener un método translate. ¿Hay una API diferente para esto en versiones más nuevas? –

+1

yo no entiendo muy bien, pero [a este tema] (http://stackoverflow.com/questions/1144906/error-running-heckle-currentcode-undefined-method-translate-for-ruby2ruby) recomienda la degradación hasta 1,2 Ruby2Ruby .2. También encontré [este parche de mono] (http://gist.github.com/321038) que afirma agregarlo a versiones posteriores. No lo he probado sin embargo. –

+1

Es un asco que no funciona con Ruby 1.9 ... – Kludge

11

Uso sourcify

Esto funcionará en Rubí 1.8 o 1.9.

def save_for_later(&block) 
    block.to_source 
end 

x = 40 
s = save_for_later {|y| x + y } 
# => "proc { |y| (x + y) }" 
g = eval(s) 
# => #<Proc:[email protected](eval):1> 
g.call(2) 
# => 42 

Ver my other answer para capturar variables libres.

actualización: Ahora usted también puede usar la gema serializable_proc, que utiliza sourcify, y captura instancia local, clase y variables globales.

+0

¿Hay algún ejemplo en alguna parte del uso de sourcify con la captura de variables gratuitas? – rogerdpack

+0

@rogerdpack, mira mi actualización. Puede que quiera usar la gema serializable_proc si le importan las variables gratuitas. –

+0

'sourcify' /' SerializableProc': ya no se mantiene (al menos en la actualidad) – ribamar

Cuestiones relacionadas