如何在Ruby中序列化lambda表达式(Proc)?

21

Joe Van Dyk 向Ruby邮件列表提问

你好,

在Ruby中,我猜你不能封送(marshal)一个lambda/proc对象,对吗?在lisp或其他语言中是否可能呢?

我尝试做的事情:

l = lamda { ... }
Bj.submit "/path/to/ruby/program", :stdin => Marshal.dump(l)
所以,我将一个lambda对象发送给BackgroundJob,其中包含了要执行的上下文/代码。但是,似乎这是不可能的。最终我编组了一个普通Ruby对象,其中包含程序运行后要执行的指令。
Joe
7个回答

21

无法对Lambda或Proc进行编组。这是因为它们都被视为闭包,这意味着它们会关闭在其定义时所涉及到的内存并引用它。(为了编组它们,您必须编组它们在创建时可以访问的所有内存。)

然而,正如Gaius所指出的那样,您可以使用ruby2ruby来获取程序的字符串。也就是说,您可以对表示Ruby代码的字符串进行编组,然后稍后重新评估它。


3
ruby2ruby 只适用于 1.8 版本,目前还没有官方的方法来反序列化 1.9 版本的字节码。 - manveru
1
Ruby2ruby 在 MRI 1.9 上运行已有一段时间了。Ripper 也很酷,它自从 1.9 版本开始就随着 MRI 一起发布了。 - Wojciech Kaczmarek

12

您也可以将代码输入字符串中:

code = %{
    lambda {"hello ruby code".split(" ").each{|e| puts e + "!"}}
}

然后使用eval执行它

eval code

这将返回一个Ruby lambda。

使用%{}格式会转义字符串,但只会在未匹配的括号上关闭。例如,您可以像这样嵌套大括号%{ [] {} },它仍然被包含在内。

大多数文本语法突出显示器并不知道这是一个字符串,因此仍然显示常规代码突出显示。


4
如果您想获取使用Ruby2Ruby的字符串版本的Ruby代码,您可能会喜欢这个主题

3

1

这是一个有趣的解决方案,虽然我认为它看起来需要在磁盘上存在 proc 的源代码。这是有道理的;将 YARV 字节码反编译回 AST 可能不是微不足道的(可能不可能)。当原始 AST 不再需要时,YARV 会丢弃它,但自定义字节码编译器可以保留原始 AST,从而使这种技术也适用于动态生成的 proc(即使用 "eval" 创建的)。 - Paul Brannan

1
从前,使用ruby-internal gem(https://github.com/cout/ruby-internal)可以实现这一点,例如:
p = proc { 1 + 1 }    #=> #<Proc>
s = Marshal.dump(p)   #=> #<String>
u = Marshal.load(s)   #=> #<UnboundProc>
p2 = u.bind(binding)  #=> #<Proc>
p2.call()             #=> 2

有一些注意事项,但已经过去多年了,我无法记住细节。例如,如果变量是绑定中的dynvar,并在其中转储,而在重新绑定的绑定中是局部变量,则不确定会发生什么。在MRI上序列化AST或在YARV上序列化字节码都是非常复杂的。
上面的代码适用于YARV(高达1.9.3)和MRI(高达1.8.7)。没有理由不能使其在Ruby 2.x上工作,只需要付出少量努力即可。

0
如果将proc定义到文件中,您可以获取proc的文件位置并对其进行序列化,然后在反序列化后使用该位置再次获取proc。
代码如下:

proc_location_array = proc.source_location

反序列化后:

file_name = proc_location_array[0]

line_number = proc_location_array[1]

proc_line_code = IO.readlines(file_name)[line_number - 1]

proc_hash_string = proc_line_code[proc_line_code.index("{")..proc_line_code.length]

proc = eval("lambda #{proc_hash_string}")


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接