Как переменные связаны с телом define_method? - PullRequest
5 голосов
/ 08 января 2010

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

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

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

Ответы [ 3 ]

6 голосов
/ 08 января 2010

Блоки в 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 голос
/ 09 января 2010
for name in [ :new, :create, :destroy ]
  local_name = name
  define_method("test_#{local_name}") do
    puts local_name
  end
end

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

0 голосов
/ 08 января 2010

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

Если вы действительно посмотрите на поведение выражений цикла for в черновой спецификации рубина ISO, вы обнаружите, что выражение цикла 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
...