如何在 Ruby 中封送 lambda (Proc)?
-
09-06-2019 - |
题
乔·范戴克 询问 Ruby 邮件列表:
你好,
在 Ruby 中,我猜你不能封送 lambda/proc 对象,对吗?在LISP或其他语言中可能可以吗?
我想做什么:
l = lamda { ... }
Bj.submit "/path/to/ruby/program", :stdin => Marshal.dump(l)
因此,我正在向BackgroundJob发送一个lambda对象,该对象包含有关做什么的上下文/代码。但是,我猜这是不可能的。我最终建立了一个普通的红宝石对象,该对象包含有关该程序运行后要做什么的说明。
乔
解决方案
您无法封送 Lambda 或 Proc。这是因为它们都被视为闭包,这意味着它们围绕定义它们的内存封闭并可以引用它。(为了整理它们,您必须整理它们在创建时可以访问的所有内存。)
正如盖乌斯指出的那样,你可以使用 红宝石2红宝石 获取程序的字符串。也就是说,您可以整理表示 ruby 代码的字符串,然后稍后重新评估它。
其他提示
您也可以仅以字符串形式输入代码:
code = %{
lambda {"hello ruby code".split(" ").each{|e| puts e + "!"}}
}
然后用 eval 执行它
eval code
这将返回一个 ruby lamda。
使用 %{}
format 对字符串进行转义,但仅以不匹配的大括号结束。IE。你可以像这样嵌套大括号 %{ [] {} }
而且它仍然是封闭的。
大多数文本语法荧光笔没有意识到这是一个字符串,因此仍然显示常规代码突出显示。
如果您有兴趣使用 Ruby2Ruby 获取 Ruby 代码的字符串版本,您可能会喜欢 这个线程.
尝试 红宝石2红宝石
我发现 proc_to_ast 做得最好: https://github.com/joker1007/proc_to_ast.
在 ruby 2+ 中肯定有效,我已经为 ruby 1.9.3+ 兼容性创建了一个 PR(https://github.com/joker1007/proc_to_ast/pull/3)
如果proc被定义到一个文件中,你可以获取proc的文件位置然后将其序列化,然后在反序列化后使用该位置再次返回到proc
proc_location_array = proc.source_location
反序列化后:
文件名 = proc_location_array[0]
行号 = proc_location_array[1]
proc_line_code = IO.readlines(文件名)[行号 - 1]
proc_hash_string = proc_line_code[proc_line_code.index("{")..proc_line_code.length]
proc = eval("lambda #{proc_hash_string}")
曾几何时,使用 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
有一些警告,但已经很多年了,我记不清细节了。举个例子,我不确定如果一个变量在转储的绑定中是动态变量,而在重新绑定的绑定中是本地变量,会发生什么。序列化 AST(在 MRI 上)或字节码(在 YARV 上)并非易事。
上面的代码适用于 YARV(最高 1.9.3)和 MRI(最高 1.8.7)。没有理由不让它在 Ruby 2.x 上工作,只需付出少量的努力。