2008-10-14 11 views
16

Je veux être capable d'écrire un lambda/Proc dans mon code Ruby, le sérialiser pour que je puisse l'écrire sur le disque, puis exécuter le lambda plus tard. Un peu comme ...Comment pouvez-vous sérialiser/sérialiser le code Ruby?

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

Plus tard, dans une course 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, et en Lisp c'est trivial. Mais y a-t-il un moyen de le faire dans Ruby? En d'autres termes, quelle serait la mise en œuvre de save_ pour _ plus tard?

Modifier: My answer below est agréable, mais il ne se ferme pas sur les variables libres (comme x) et sérialisation 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 ne comprend pas une définition pour x. Existe-t-il une solution qui en tienne compte, peut-être en sérialisant la table des symboles? Pouvez-vous accéder à cela dans Ruby?

Édition 2: J'ai mis à jour ma réponse pour incorporer les variables locales de sérialisation. Cela semble acceptable.

Répondre

11

Utilisez 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 

Cela tombe bien, mais il ne se ferme pas sur les variables libres (comme x) et sérialisation avec le lambda.

Pour serialize variables également, vous pouvez itérer sur local_variables et les sérialiser également. Le problème, cependant, est que local_variables de save_for_later accède seulement c et s dans le code ci-dessus - c'est-à-dire les variables locales au code de sérialisation, pas l'appelant. Donc, malheureusement, nous devons pousser l'accaparement des variables locales et de leurs valeurs à l'appelant.

Peut-être que c'est une bonne chose, car, en général, trouver toutes les variables libres dans un morceau de code Ruby est undecidable. De plus, idéalement, nous devrions également enregistrer global_variables et toutes les classes chargées et leurs méthodes substituées. Cela semble impraticable.

En utilisant cette approche simple, vous obtenez les éléments suivants:

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

Ma version de Ruby2Ruby (1.2.4) ne semble pas avoir de méthode de traduction. Y a-t-il une API différente pour cela dans les nouvelles versions? –

+1

Je ne comprends pas très bien, mais [ce thread] (http://stackoverflow.com/questions/1144906/error-running-heckle-currentcode-undefined-method-translate-for-ruby2ruby) recommande de revenir à Ruby2Ruby 1.2 .2. J'ai également trouvé [ce patch de singe] (http://gist.github.com/321038) qui prétend l'ajouter à des versions ultérieures. Je n'ai pas essayé cependant. –

+1

Il craint que cela ne fonctionne pas avec Ruby 1.9 ... – Kludge

11

Utilisez sourcify

Cela fonctionne 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:[email protected](eval):1> 
g.call(2) 
# => 42 

Voir my other answer pour capturer des variables libres.

Mise à jour: Maintenant, vous pouvez également utiliser la pierre précieuse serializable_proc, qui utilise sourcify et capture locale, par exemple, la classe et les variables globales.

+0

Existe-t-il un exemple d'utilisation de sourcify avec la capture de variables libres? – rogerdpack

+0

@rogerdpack, voir ma mise à jour. Vous pouvez juste vouloir utiliser la gemme serializable_proc si vous vous souciez des variables libres. –

+0

'sourcify' /' SerializableProc': n'est plus maintenu (au moins aujourd'hui) – ribamar

Questions connexes