3

define_method 表现出以下行为:

class TestClass
  def exec_block(&block) ; yield ; end
end
TestClass.new.send(:exec_block) do ; puts self ; end
# -> main
TestClass.send(:define_method, :bing) do ; puts self ; end
TestClass.new.bing
# -> <TestClass:...>

我不明白的是传递给 define_method 的块应该是一个闭包。因此,它应该(至少根据我的理解)捕获 as 的值,selfmain调用时所展示的那样exec_block

我知道该块将成为该方法的主体,但我不明白该行为的原因。为什么当使用不同的方法时,块会评估不同的东西?

如何使用define_method其他方法重现块的行为?即我怎么能写exec_block出来让它输出<TestClass:...>而不是'main'?

4

3 回答 3

5

self像任何其他变量一样被闭包捕获。Proc我们可以通过传递不同的对象实例来验证这一点:

class A
  def exec_block(&block)
    block.call
  end
end

class B
  def exec_indirect(&block)
    A.new.exec_block(&block)
  end
end

block = proc { p self }
a = A.new; b = B.new

a.exec_block(&block)    # => main
b.exec_indirect(&block) # => main

但是,BasicObject#instance_eval类似动态重新绑定self变量:

为了设置上下文,在代码执行时将变量self设置为obj,让代码可以访问obj 的实例变量

Module#define_method反过来用于instance_eval执行关联的块:

如果指定了块,则将其用作方法体。使用 instance_eval [...] 评估此块

观察:

A.send(:define_method, :foo, &block)
a.foo                   # => #<A:0x00000001717040>
a.instance_eval(&block) # => #<A:0x00000001717040>

有了这些知识,您现在可以重写您exec_block的使用instance_eval

class A
  def exec_block(&block)
    instance_eval(&block)
  end
end

block = proc { p self }
A.new.exec_block(&block)  # => #<A:0x00000001bb9828>

如前所述,使用instance_eval似乎是运行Proc具有修改上下文的实例的唯一方法。它可用于在 Ruby中实现动态绑定。

于 2012-04-18T14:09:15.787 回答
0

灵感来自 Niklas B. 的评论:

class TestClass
  def exec_block(&block) ; yield ; end
end
s = self

TestClass.new.send(:exec_block) do ; puts s ; end
# -> main

TestClass.send(:define_method, :bing) do ; puts s ; end
TestClass.new.bing
# -> main
于 2012-04-18T12:51:47.707 回答
0

第一件事 - 当您将块显式传递给方法而不是yield您可以使用block.call. 第二件事-内部exec_block方法替换yieldinstance_eval(&block),您将看到魔力;)

再澄清一点 - 在第一个示例中,该块捕获本地范围以及self指向main对象的变量。

在第二个示例中(使用define_method),该块将被视为一个新的方法体,并将在对象的范围内使用 进行评估instance_eval。有关更多详细信息,您可以查看:http ://apidock.com/ruby/Module/define_method

于 2012-04-18T10:20:22.617 回答