Я довольно плохо знаком с Ruby, и часть логики закрытия имеет меня запутанное. Рассмотрите этот код:
array = []
for i in (1..5)
array << lambda {i}
end
array.map{|f| f.call} # => [5, 5, 5, 5, 5]
Это имеет смысл мне, потому что я связываюсь вне цикла, таким образом, та же переменная получена каждым прохождением через цикл. Это также имеет смысл мне, что использование каждого блока может зафиксировать это:
array = []
(1..5).each{|i| array << lambda {i}}
array.map{|f| f.call} # => [1, 2, 3, 4, 5]
... потому что я теперь объявляюсь отдельно в течение каждого раза через. Но теперь я заблудился: почему я не могу также зафиксировать его путем представления промежуточной переменной?
array = []
for i in 1..5
j = i
array << lambda {j}
end
array.map{|f| f.call} # => [5, 5, 5, 5, 5]
Поскольку j является новым каждый раз через цикл, я думал бы, что другая переменная будет получена на каждой передаче. Например, это определенно, как C# работает, и как - я думаю - Lisp ведет себя с позволенным. Но в Ruby не так. Что действительно происходит?
Править: См. комментарии в ответах; проблема, кажется, что j находится все еще в объеме вне цикла. Как действительно определяет объем в циклах, действительно работают?
Править: Я предполагаю, что все еще не понимаю; если циклы не создают новые объемы, почему это:
for i in 1..5
puts j if i > 1 #undefined local variable or method `j' for main:Object (NameError)
j = i
end
Ладно, это уже смешно. Каждый раз, когда я пытаюсь ответить на вопрос о том, как циклы for
работают в Ruby, я ошибаюсь.
Причина этого, конечно же, в том, что я не использую для
циклов в Ruby, как и никто другой, так что для меня это действительно не имеет значения: -)
В любом случае , чтобы решить этот вопрос раз и навсегда, я сразу обратился к первоисточнику , предварительному проекту от 1 декабря 2009 г. спецификации языка Ruby IPA (призванной стать спецификацией языка ISO Ruby):
§11.4.1.2.3
для
выражениеСинтаксис
- для-выражения → для для-переменной в выражение do-clause end
- for-variable → левая сторона | множественная левая сторона
выражение из выражения for не должно быть выражением перехода .
Семантика
A for-expression оценивается следующим образом:
- Вычислить выражение . Пусть
O
будет результирующим значением.Пусть
E
будет вызовом-первичным методом формы первичным-выражением [здесь нет ограничителя строки] . каждый do | список-формальных-аргументов блока | тело-блока конец , где значение первичного-выражения равноO
, список-формальных-аргументов блока - это для переменной , тело блока - это составной оператор ] из do-clause .Оцените
E
, но пропустите шаг c из §11.2.2.Значение выражения for является результирующим значением вызова.
Хорошо, в основном это означает, что
for for_variable in expression
do_clause
end
переводится в
O = expression
O.each do |for_variable|
do_clause
end
Или, в вашем случае:
for i in 1..5
puts j if i > 1 #undefined local variable or method `j' (NameError)
j = i
end
переводится в
(1..5).each do |i|
puts j if i > 1 #no excpetion here, works just fine ??!!??
j = i
end
Ага! Но мы кое-что забыли! Это зловещее «пропустить шаг c из §11.2.2». вещь! Итак, что он говорит?
- Вставьте пустой набор привязок локальных переменных в «привязки локальных переменных».
Обратите внимание, что на этапе b
- контекст выполнения устанавливается равным
E
b
.
- это , а не пропущено.
Итак, насколько я понимаю, цикл for
получает свой собственный контекст выполнения, который начинается как копия текущего контекста выполнения, но он не получает собственный набор привязок локальных переменных. IOW: он получает свой собственный динамический контекст выполнения, но не собственную лексическую область видимости.
Должен признать, я все еще не уверен, что полностью понимаю это, но нет более точного, чем это.
На какой версии Ruby вы это делаете? В 1.8 нет области видимости блока для локальных переменных, поэтому j все еще висит (и равна 5) даже после окончания for
.