Frage

Ich möchte in die Lage, eine Lambda / Proc in meinem Ruby-Code zu schreiben, es serialisiert werden, so dass ich es auf der Festplatte schreiben kann, und dann später das Lambda auszuführen. Art wie ...

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

Später, in einem separaten Laufe des Ruby-Interpreter, ich möchte sagen können ...

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

Marshal.dump funktioniert nicht für Procs. Ich weiß, Perl hat Daten Dump :: :: Streamer und in Lisp ist dies trivial. Aber gibt es eine Möglichkeit, es in Ruby zu tun? Mit anderen Worten, was die Umsetzung der save_for_later wäre?

Bearbeiten : Meine Antwort unten ist schön, aber es ist in der Nähe, sie freie Variablen nicht über (wie x) und serialisiert 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 die Symboltabelle Serialisierung? Können Sie das in Ruby zugreifen?

Edit 2 : Ich habe meine Antwort aktualisiert lokale Variablen zu übernehmen Serialisierung. Dies scheint akzeptabel.

War es hilfreich?

Lösung

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

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

Um Variablen serialisiert auch, können Sie iterieren local_variables und serialisiert sie als gut. Das Problem ist allerdings, dass local_variables aus save_for_later greift nur c und s in dem obigen Code - das heißt Variablen lokal auf den Serialisierungscode, nicht der Anrufer. Also leider müssen wir die Grabbing von lokalen Variablen und deren Werte an den Aufrufer drücken.

Vielleicht ist das eine gute Sache, aber, weil in der Regel in einem Stück Ruby-Code alle freien Variablen zu finden, ist unentscheidbar . Plus, im Idealfall würden wir auch global_variables und alle geladenen Klassen und ihre überschriebenen Methoden speichern. Dies scheint nicht praktikabel.

Mit diesem einfachen Ansatz, erhalten Sie wie folgt vor:

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

Andere Tipps

Verwenden Sie sourcify

Dies funktioniert auf Ruby 1.8 oder 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

Siehe meine andere Antwort für die Aufnahme frei Variablen.

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

die Antworten Schauen Sie sich diese Frage .

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top