Как переменные связываются с телом define_method?

При попытке повторить мои навыки Ruby я продолжаю бежать через этот случай, которого я не могу выяснить объяснение, просто читая документы API. Объяснение значительно ценилось бы. Вот пример кода:

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

То, что я хочу/ожидаю произойти, то, что name переменная будет связана с блоком, данным define_method и это, когда #test_new назван это произведет "новый". Вместо этого каждый определенный метод выводы "уничтожает" - последнее значение, присвоенное переменной имени. О чем я неправильно понимаю define_method и его блоки?Спасибо!

5
задан Chris 8 January 2010 в 17:49
поделиться

3 ответа

Блоки в Ruby являются замыканиями: блок, который вы передаете в define_method , захватывает саму переменную name , а не ее значение, поэтому она остается в области видимости при каждом вызове этого блока. Это первая часть головоломки.

Во-вторых, метод, определенный в define_method , является самим блоком. По сути, он преобразует объект Proc (переданный ему блок) в объект Method и привязывает его к получателю.

В итоге вы получите метод , который захватил (закрыл) переменную name , которая к моменту завершения вашего цикла будет установлена ​​на : уничтожить .

Дополнение: Конструкция 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
6
ответ дан 14 December 2019 в 08:52
поделиться
for name in [ :new, :create, :destroy ]
  local_name = name
  define_method("test_#{local_name}") do
    puts local_name
  end
end

Этот метод будет вести себя так, как вы ожидаете. Причина путаницы в том, что «имя» не создается один раз за итерацию цикла for. Он создается один раз и увеличивается. Кроме того, если я правильно понимаю, определения методов не являются закрытием, как другие блоки. Они сохраняют видимость переменных, но не закрывают текущее значение переменных.

1
ответ дан 14 December 2019 в 08:52
поделиться

Проблема здесь заключается в том, что для выражений петлей не создает новую область. Единственные вещи, которые создают новые области на рубин, являются телами сценария, органы модуля, классовые тела, метод тел и блоки.

Если вы на самом деле посмотрите поведение для выражений петлей в черновом спецификации ISO Ruby, вы обнаружите, что экспрессия для выражение петли выполняется точно так же, как каждый Итератор , за исключением для того, что он не создает новую область.

Ни один рубист никогда не будет использовать петлю для , в любом случае: вместо этого они будут использовать итератор, который делает , принимает блок и, таким образом, создает новый объем.

Если вы используете идиоматический итератор, все работает, как и ожидалось:

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
0
ответ дан 14 December 2019 в 08:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: