Pregunta

Quiero poder escribir un lambda / Proc en mi código 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 Datos :: Dump :: Streamer , y en Lisp esto es trivial. ¿Pero hay alguna manera de hacerlo en Ruby? En otras palabras, ¿cuál sería la implementación de save _ para _ later ?

Editar : Mi respuesta a continuación es agradable, 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 . ¿Existe una solución que tenga esto en cuenta, tal vez serializando la tabla de símbolos? ¿Puedes acceder a eso en Ruby?

Editar 2 : Actualicé mi respuesta para incorporar la serialización de variables locales. Esto parece aceptable.

¿Fue útil?

Solución

Usar 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 variables también, puede iterar sobre local_variables y serializarlos también. El problema, sin embargo, es que local_variables desde save_for_later solo accede a c y s en el código anterior - es decir, variables locales al código de serialización, no la persona que llama. Desafortunadamente, debemos llevar la captura de 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 un fragmento de código Ruby es indecidible . Además, idealmente también guardaríamos global_variables y cualquier clase cargada y sus métodos anulados. 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

Otros consejos

Utilice 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 gratis variables.

Actualización : Ahora también puede usar la serializable_proc , que usa sourcify y captura local, instancia, clase y variables globales.

Consulte las respuestas a esta pregunta .

Ruby tiene la clase Marshal que tiene un método de volcado al que puede llamar.

Echa un vistazo aquí:

http://rubylearning.com/satishtalim/object_serialization.html

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top