2010-01-08 3 views
4

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

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

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

+0

Теперь я думаю, что я должен ожидать, что define_method будет работать так же, как ключевое слово def, и в этом случае блок, заданный define_method, может работать только с переменными, локальными для него или переданными в качестве аргументов. – Chris

ответ

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

Думаю, я понял это сейчас. Похоже, вы нашли источник путаницы, хотя явно не назвали его: проблема заключается не столько в том, что блок, переданный 'define_method', является закрытием (на самом деле это именно то, что хочет OP), но что тело выражения 'for' не является замыканием. Выражение 'for' создает новую * локальную переменную * внутри * области, в которой определяется выражение' for'. Итератор 'each' OTOH принимает блок, который, конечно, является закрытием, что означает, что теперь' name' ссылается на конкретный экземпляр переменной «name» block для этой итерации * –

+0

Oh, true. И фактически переменная, созданная выражением 'for ... in', по-прежнему находится в области * после * цикла, а не только в теле цикла (поскольку я тестирую его в' irb'). – Kevin

0

Проблема здесь состоит в том, что выражения 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 
+2

«Рубинист никогда не будет ...» - это необоснованное утверждение. Многие из нас находят для циклов гораздо более смысловыми и лаконичными, чем «каждый». – scrozier

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

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

+1

Углубленный взгляд на ту же проблему (и решение) на C#: http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful .aspx – kejadlen