变量如何绑定到define_method的主体?

在尝试提高我的Ruby技能时,我一直在讨论这个案例,我只能通过阅读API文档找不到解释。 非常感谢您的解释。 这是示例代码:

for name in [ :new, :create, :destroy ] define_method("test_#{name}") do puts name end end 

我希望/期望发生的是name变量将绑定到给定于define_method的块,并且当#test_new时它将输出“new”。 而是每个定义的方法输出“destroy” – 分配给name变量的最后一个值。 我对define_method及其块有什么误解? 谢谢!

Ruby中的块是闭包:传递给define_method的块捕获变量name本身 – 而不是它的值 – 因此只要调用该块,它就会保留在作用域中。 这是拼图的第一部分。

第二部分是define_method定义的define_method 块本身。 基本上,它将Proc对象(传递给它的块)转换为Method对象,并将其绑定到接收器。

所以你最终得到的是一个捕获(关闭)变量name ,当你的循环完成时,它被设置为:destroy

另外: for ... in实际创建了一个新的局部变量,相应的[ ... ].each {|name| ... } [ ... ].each {|name| ... }建设不行 。 也就是说,你的for ... in循环等价于以下(无论如何都在Ruby 1.8中):

 name = nil [ :new, :create, :destroy ].each do |name| define_method("test_#{name}") do puts name end end name # => :destroy 
 for name in [ :new, :create, :destroy ] local_name = name define_method("test_#{local_name}") do puts local_name end end 

此方法将按预期运行。 混淆的原因是每次迭代for循环都不会创建一次’name’。 它创建一次,并递增。 另外,如果我理解正确,方法定义不是像其他块一样的闭包。 它们保留可变可见性,但不会关闭变量的当前值。

这里的问题是for循环表达式不会创建新的范围。 在Ruby中创建新范围的唯一事情是脚本体,模块体,类体,方法体和块。

如果您实际上在草案ISO Ruby规范中查找for循环表达式的行为,您会发现for循环表达式的执行方式与each迭代器完全相同除非它不创建新范围。

无论如何,Rubyist都不会使用for循环:它们会使用迭代器,它占用一个块,从而创建一个新的范围。

如果你使用一个惯用的迭代器,一切都按预期工作:

 class Object %w[new create destroy].each do |name| define_method "test_#{name}" do puts name end end end require 'test/unit' require 'stringio' class TestDynamicMethods < Test::Unit::TestCase def setup; @old_stdout, $> = $>, (@fake_logdest = StringIO.new) end def teardown; $> = @old_stdout end def test_that_the_test_create_method_prints_create Object.new.test_create assert_equal "create\n", @fake_logdest.string end def test_that_the_test_destroy_method_prints_destroy Object.new.test_destroy assert_equal "destroy\n", @fake_logdest.string end def test_that_the_test_new_method_prints_new Object.new.test_new assert_equal "new\n", @fake_logdest.string end end