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

时间:2010-01-08 17:49:49

标签: ruby reflection metaprogramming

在尝试提高我的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及其障碍有什么误解?谢谢!

3 个答案:

答案 0 :(得分:6)

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

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

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

添加: for ... in构造实际上会创建一个新的局部变量,相应的[ ... ].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

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

这里的问题是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