【发布时间】:2022-06-11 10:14:30
【问题描述】:
Ruby 2.7.0
我正在写一些类似于“eval”方法的东西,我意识到似乎发生了一些神奇的事情。
Ruby 的 Kernel#eval 能够获得绑定,但不知何故,当未指定绑定时,它设法将调用者绑定作为默认值。
举个例子:
def my_eval(str)
"?? how do I access #{str}"
#foo # <- NameError
end
foo = "BAR"
var = 'foo'
puts "EVAL: "+eval(var)
puts "MY_EVAL: "+my_eval(var)
我们用'var'调用eval,它是一个包含单词“foo”的字符串。不知何故,eval 能够访问调用者 local_variables,就好像它被神奇地用 'binding()' 调用了
my_eval 方法就没那么幸运了。如何获取调用者绑定或让绑定随方法调用自动发送,而无需要求该方法的所有用户将“binding()”添加到参数列表中?
或者这种魔法是否仅限于 Kernel#eval 并且无法复制?
编辑:
发现类似问题后:How can I get the binding from method_missing?
我使用 TracePoint(set_trace_func 的干净版本)提出了一个(稍微不那么脆弱的)答案。
但这需要在我的代码之上运行一个简单的调试器,这不仅令人担忧,而且大大降低了我的代码速度(简单的绑定跟踪运行速度慢了 4 倍!)。 ruby 是否有这种类型的内部反射而不使用跟踪器,或者可能将 my_eval 编写为 C 函数?
【问题讨论】:
-
我发现了“binding_of_caller”gem,它依赖于 RubyVM::DebugInspector(至少对于 MRI),它是另一个具有 C 扩展的 gem,这让我开始怀疑这是做不到的。我很惊讶地看到 eval 可能在这方面作弊。 :(
-
请注意,如果您创建一个方法
def kernel_eval(str); eval(str); end;并调用为kernel_eval(var)结果是相同的,因为foo在该范围内未定义。 -
@engineersmnky 正是这一点。那么我们如何让 kernel_eval 以与 eval 相同的方式获取调用者的范围呢? eval 只是一种方法。
-
每一种编程语言都会有一些基本的操作无法在编程语言本身中解释。在 Ruby 中,其中包括直接与内存交互的
Class#allocate、Module#private、Module#protected、Module#public、Module#define_method、Module#define_singleton_method,以及各种*_eval和*_exec方法.例如,在其他语言中,考虑 Java 有重载运算符但没有重载运算符。 -
@DavidLjungMadisonStellar 另外,您可以查看 rubinius(最初是用 ruby 编写的 ruby VM)源代码,了解它们如何实现 Kernel#eval 和 Kernel#caller。虽然我没有花时间去追踪它,但它可以为您提供一个有用的方向。