I want to be able to write a lambda/Proc in my Ruby code, serialize it so that I can write it to disk, and then execute the lambda later. Sort of like...
x = 40
f = lambda { |y| x + y }
save_for_later(f)
Later, in a separate run of the Ruby interpreter, I want to be able to say...
f = load_from_before
z = f.call(2)
z.should == 42
Marshal.dump does not work for Procs. I know Perl has Data::Dump::Streamer, and in Lisp this is trivial. But is there a way to do it in Ruby? In other words, what would be the implementation of save
?_
for_
later
Edit: My answer below is nice, but it does not close over free variables (like x
) and serialize them along with the lambda. So in my example ...
x = 40
s = save_for_later { |y| x + y }
# => "lambda { |y|\n (x + y)\n}"
... the string output does not include a definition for x
. Is there a solution that takes this into account, perhaps by serializing the symbol table? Can you access that in Ruby?
Edit 2: I updated my answer to incorporate serializing local variables. This seems acceptable.
-
Ruby has the Marshal class that has a dump method that you can call.
Take a look here:
http://rubylearning.com/satishtalim/object_serialization.html
Jonathan Tran : Marshal doesn't do lambdas.Kyle Burton : Marshal doesn't actually work on Procs, which what the question is asking about.From Mark -
Check out the answers to this question.
From James A. Rosen -
Use 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
This is great, but it does not close over free variables (like
x
) and serialize them along with the lambda.To serialize variables also, you can iterate over
local_variables
and serialize them as well. The problem, though, is thatlocal_variables
from withinsave_for_later
accesses onlyc
ands
in the code above -- i.e. variables local to the serialization code, not the caller. So unfortunately, we must push the grabbing of local variables and their values to the caller.Maybe this is a good thing, though, because in general, finding all free variables in a piece of Ruby code is undecidable. Plus, ideally we would also save
global_variables
and any loaded classes and their overridden methods. This seems impractical.Using this simple approach, you get the following:
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
Stephen McCarthy : My version of Ruby2Ruby (1.2.4) doesn't seem to have a translate method. Is there a different api for this in newer versions?Jonathan Tran : I don't quite understand it, but [this thread](http://stackoverflow.com/questions/1144906/error-running-heckle-currentcode-undefined-method-translate-for-ruby2ruby) recommends downgrading to Ruby2Ruby 1.2.2. I also found [this monkey patch](http://gist.github.com/321038) which claims to add it back to later versions. Haven't tried it though.From Jonathan Tran -
inspect often prints in a format that can be eval'd.
From John Conti
0 comments:
Post a Comment