2008-10-14 6 views
16

Ich möchte in der Lage sein, ein Lambda/Proc in meinem Ruby-Code zu schreiben, serialisieren, so dass ich es auf die Festplatte schreiben kann, und später das Lambda ausführen. Art wie ...Wie können Sie Ruby Code stringisieren/serialisieren?

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

Später, in einem separaten Lauf des Ruby-Interpreter, ich will für Procs zu der Lage sein, zu sagen ...

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

Marshal.dump funktioniert nicht. Ich weiß, Perl hat Data::Dump::Streamer, und in Lisp ist dies trivial. Aber gibt es einen Weg, es in Ruby zu machen? Mit anderen Worten, was wäre die Implementierung von save_ für _ später?

bearbeiten: My answer below ist schön, aber es lässt sich nicht schließen freie Variablen über (wie x) und serialisiert sie mit dem Lambda zusammen. Also in meinem Beispiel ...

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

... die Zeichenfolge Ausgabe enthält keine Definition für x. Gibt es eine Lösung, die dies berücksichtigt, vielleicht durch Serialisierung der Symboltabelle? Kannst du in Ruby darauf zugreifen?

Edit 2: Ich habe meine Antwort aktualisiert, um serialisierende lokale Variablen zu integrieren. Dies scheint akzeptabel.

Antwort

11

Verwenden 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 

Das ist großartig, aber es lässt sich nicht schließen freie Variablen (wie x) über und serialisiert sie mit dem Lambda zusammen.

Um serialize variables können Sie auch über local_variables iterieren und sie auch serialisieren. Das Problem besteht jedoch darin, dass local_variables innerhalb von nur auf c und s in dem obigen Code zugreift - d. H. Variablen, die für den Serialisierungscode lokal sind, nicht der Aufrufer. Daher müssen wir leider das Ergreifen lokaler Variablen und deren Werte auf den Aufrufer übertragen.

Vielleicht ist dies eine gute Sache, denn im Allgemeinen ist das Finden aller freien Variablen in einem Stück Ruby-Code undecidable. Plus, im Idealfall würden wir auch global_variables und alle geladenen Klassen und ihre überschriebenen Methoden speichern. Dies scheint unpraktisch zu sein.

diesen einfachen Ansatz, erhalten Sie die folgenden Schritte aus:

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

Meine Version von Ruby2Ruby (1.2.4) scheint keine Übersetzungsmethode zu haben. Gibt es dafür in neueren Versionen eine andere API? –

+1

Ich verstehe es nicht ganz, aber [dieses thread] (http://stackoverflow.com/questions/1144906/error-running-heckle-current-code-undefined-method-translate-for-ruby2ruby) empfiehlt Downgrade auf Ruby2Ruby 1.2 .2. Ich fand auch [diesen Affe-Patch] (http://gist.github.com/321038), der behauptet, es zu späteren Versionen hinzuzufügen. Habe es aber noch nicht ausprobiert. –

+1

Es saugt, dass es nicht mit Ruby 1.9 funktioniert ... – Kludge

11

Verwenden sourcify

Dies wird 1.8 oder 1.9 auf Rubin arbeiten.

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 

Siehe my other answer für freie Variablen erfassen.

aktualisieren: Jetzt können Sie auch das serializable_proc Juwel verwenden, die sourcify verwendet und erfasst lokale Instanz, Klasse und globale Variablen.

+0

Gibt es irgendwo ein Beispiel für die Verwendung von Sourcify mit der Erfassung von freien Variablen? – rogerdpack

+0

@rogerdpack, siehe mein Update. Vielleicht möchten Sie einfach das serializable_proc-Juwel verwenden, wenn Sie an freien Variablen interessiert sind. –

+0

'sourcify' /' SerializableProc': nicht mehr gepflegt (mindestens heute) – ribamar