Question

Je veux pouvoir écrire un lambda / Proc dans mon code Ruby, le sérialiser pour pouvoir l'écrire sur le disque, puis exécuter le lambda plus tard. Un peu comme ...

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

Plus tard, dans une exécution séparée de l'interpréteur Ruby, je veux pouvoir dire ...

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

Marshal.dump ne fonctionne pas pour Procs. Je sais que Perl a Data :: Dump :: Streamer , ce qui est trivial en Lisp. Mais existe-t-il un moyen de le faire en Ruby? En d'autres termes, quelle serait la mise en oeuvre de enregistrer _ pour _ plus tard ?

Modifier : Ma réponse ci-dessous est agréable, mais il ne ferme pas les variables libres (comme x ) et ne les sérialise pas avec le lambda. Donc, dans mon exemple ...

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

... la sortie de chaîne n'inclut pas de définition pour x . Existe-t-il une solution tenant compte de cela, peut-être en sérialisant la table des symboles? Pouvez-vous accéder à cela en Ruby?

Éditer 2 : j'ai mis à jour ma réponse pour intégrer la sérialisation des variables locales. Cela semble acceptable.

Était-ce utile?

La solution

Utiliser 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

C’est génial, mais cela ne ferme pas les variables libres (comme x ) et ne les sérialise pas avec le lambda.

Pour sérialiser les variables , vous pouvez également parcourez variables_local> et sérialisez-les également. Le problème, cependant, est que variables_local de save_for_later n’accède qu’à c et s dans le code ci-dessus - c'est-à-dire des variables locales au code de sérialisation, pas l'appelant. Donc, malheureusement, nous devons pousser la saisie des variables locales et de leurs valeurs à l'appelant.

C’est peut-être une bonne chose, car en général, trouver toutes les variables libres dans un morceau de code Ruby est indécidable . De plus, idéalement, nous enregistrerions également variables_globales ainsi que toutes les classes chargées et leurs méthodes remplacées. Cela semble peu pratique.

En utilisant cette approche simple, vous obtenez ce qui suit:

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

Autres conseils

Utilisez sourcify

Cela fonctionnera sur Ruby 1.8 ou 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

Voir mon autre réponse pour la capture gratuite variables.

Mettre à jour : Maintenant, vous pouvez également utiliser la serializable_proc gem, qui utilise sourcify et capture les instances locales, instances, classes et variables globales.

Découvrez les réponses à cette question . .

Ruby a la classe Marshal qui possède une méthode de vidage que vous pouvez appeler.

Jetez un coup d'oeil ici:

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top