16

我希望能够在我的 Ruby 代码中编写 lambda/Proc,对其进行序列化以便我可以将其写入磁盘,然后稍后执行 lambda。有点像...

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

后来,在单独运行 Ruby 解释器时,我想说...

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

Marshal.dump 不适用于 Procs。我知道 Perl 有Data::Dump::Streamer,而在 Lisp 中这是微不足道的。但是有没有办法在 Ruby 中做到这一点?换句话说, 的实施将是什么?save_for_later

编辑我下面的答案很好,但它不会关闭自由变量(如x)并将它们与 lambda 一起序列化。所以在我的例子中......

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

...字符串输出不包含x. 是否有考虑到这一点的解决方案,也许是通过序列化符号表?你可以在 Ruby 中访问它吗?

编辑 2:我更新了我的答案以包含序列化局部变量。这似乎可以接受。

4

4 回答 4

12

使用 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

这很棒,但它不会关闭自由变量(如x)并将它们与 lambda 一起序列化。

序列化变量,您也可以迭代local_variables并序列化它们。但是,问题在于仅在local_variables内部save_for_later访问cs在上面的代码中——即序列化代码的本地变量,而不是调用者。所以不幸的是,我们必须将局部变量及其值的获取推送给调用者。

不过,这也许是件好事,因为一般来说,在一段 Ruby 代码中找到所有自由变量是不可判定的。另外,理想情况下,我们还将保存global_variables任何已加载的类及其覆盖的方法。这似乎不切实际。

使用这种简单的方法,您将获得以下信息:

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
于 2008-10-14T02:16:44.040 回答
11

使用源化

这将适用于 Ruby 1.8 或 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

有关捕获自由变量,请参阅我的其他答案

更新:现在您还可以使用serializable_proc gem,它使用了 sourcify,并捕获本地、实例、类和全局变量。

于 2011-05-20T13:30:14.927 回答
2

查看这个问题的答案。

于 2008-10-14T01:40:31.633 回答
-9

Ruby 的 Marshal 类有一个可以调用的转储方法。

看看这里:

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

于 2008-10-14T00:49:11.190 回答