При попытке повторить мои навыки Ruby я продолжаю бежать через этот случай, которого я не могу выяснить объяснение, просто читая документы API. Объяснение значительно ценилось бы. Вот пример кода:
for name in [ :new, :create, :destroy ]
define_method("test_#{name}") do
puts name
end
end
То, что я хочу/ожидаю произойти, то, что name
переменная будет связана с блоком, данным define_method
и это, когда #test_new
назван это произведет "новый". Вместо этого каждый определенный метод выводы "уничтожает" - последнее значение, присвоенное переменной имени. О чем я неправильно понимаю define_method
и его блоки?Спасибо!
Блоки в 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
for name in [ :new, :create, :destroy ]
local_name = name
define_method("test_#{local_name}") do
puts local_name
end
end
Этот метод будет вести себя так, как вы ожидаете. Причина путаницы в том, что «имя» не создается один раз за итерацию цикла for. Он создается один раз и увеличивается. Кроме того, если я правильно понимаю, определения методов не являются закрытием, как другие блоки. Они сохраняют видимость переменных, но не закрывают текущее значение переменных.
Проблема здесь заключается в том, что для выражений петлей
не создает новую область. Единственные вещи, которые создают новые области на рубин, являются телами сценария, органы модуля, классовые тела, метод тел и блоки.
Если вы на самом деле посмотрите поведение для выражений петлей
в черновом спецификации 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