ruby serialization lambda proc-object

¿Cómo se escribe/serializa el código de Ruby?



serialization lambda (4)

Quiero poder escribir un lambda / Proc en mi código de Ruby, serializarlo para poder escribirlo en el disco y luego ejecutar el lambda más tarde. Algo así como...

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

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

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

Marshal.dump no funciona para Procs. 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 _ for _ later ?

Editar : mi respuesta a continuación es buena, pero no se cierra sobre las variables libres (como x ) y las serializa junto con la lambda. Entonces en mi ejemplo ...

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

... la salida de cadena 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?

Edición 2 : actualicé mi respuesta para incorporar serializar variables locales. Esto parece aceptable.




Usa 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:0x4037bb2c@(eval):1> g.call(2) # => 42

Esto es genial, pero no se cierra sobre las variables libres (como x ) y las serializa junto con la lambda.

Para serializar también variables , puede iterar sobre local_variables y serializarlas también. El problema, sin embargo, es que las variables locales dentro de save_for_later solo save_for_later a save_for_later en el código anterior, es decir, variables locales al código de serialización, no a la persona que llama. 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 indecidible . Además, idealmente también ahorraríamos global_variables y cualquier clase cargada y sus métodos reemplazados. Esto parece poco práctico.

Usando este enfoque simple, obtienes 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:0xb7cfe9c0@(eval):1> g.call(2) # => 42 # Changing x does not affect it. x = 7 g.call(3) # => 43


Use sourcify

Esto funcionará en Ruby 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:0x00000100e88450@(eval):1> g.call(2) # => 42

Vea mi otra respuesta para capturar variables gratuitas.

Actualización : ahora también puede usar la gema serializable_proc , que usa sourcify, y captura variables locales, de instancia, de clase y globales.