NameError: undefined - есть правила синтаксического анализа для локальных переменных, измененных в Ruby 2.1.2?

Я получаю NameError: undefined local variable or method с ruby ​​2.1.2

Как отмечено в этом вопросе, выражения типа:

bar if bar = true

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

bar if bar = false

Разница между ними заключается в том, оценивается ли основное тело или нет, но не имеет значения, если локальная переменная undefined сразу же вызывает ошибку перед оценкой условия.

Но когда я запускаю второй код на Ruby 2.1.2, он не вызывает ошибку. Так было раньше? Если да, то в чем состояла дискуссия по разбору? Если нет, изменилась ли спецификация Ruby? Есть ли какая-то ссылка на это? Что он сделал в 1.8.7, 1.9.3 и т.д.?

Ответ 1

Да, это изменилось в ruby ​​2.1.2

В 1.8.7, 1.9.3, 2.0.0 и даже 2.1.1 я получаю 2 предупреждения и никаких ошибок:

2.0.0-p247 :007 > bar if bar = false
(irb):7: warning: found = in conditional, should be ==
 => nil 
2.0.0-p247 :008 > bar if bar = true
(irb):8: warning: found = in conditional, should be ==
 => true 

тогда как в версии 2.1.2 вы упомянули, что я получаю 2 предупреждения и 1 NameError.

2.1.2 :001 > bar if bar = true
(irb):1: warning: found = in conditional, should be ==
NameError: undefined local variable or method `bar' for main:Object
        from (irb):1
        from /home/durrantm/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'
2.1.2 :002 > bar if bar = false
(irb):2: warning: found = in conditional, should be ==
 => nil 

Это на моем Ubuntu 14

Ответ 2

Нет никакой разницы в отношении того, определяется ли bar или нет. В обоих случаях bar undefined в теле. Однако в последнем случае тело никогда не оценивается, поэтому оно не имеет значения. Вы никогда не разрешаете имя bar, поэтому вы никогда не получите ошибку при разрешении имени.

Локальные переменные определяются при анализе присваивания. Они инициализируются при выполнении задания.

Это отлично подходит для того, чтобы переменная была унициализирована. В этом случае он будет оценивать только nil:

if false
  bar = 42
end

bar
# => nil

Однако, если переменная undefined, тогда Ruby не знает, является ли голое слово локальной переменной или бесполезным безрецептурным сообщением отправить:

foo
# NameError: undefined local variable or method `foo'
#                                     ^^^^^^^^^
# Ruby doesn't know whether it a variable or a message send

Сравните с:

foo()
# NoMethodError: undefined method `foo'
# ^^^^^^^^^^^^^

self.foo
# NoMethodError: undefined method `foo'
# ^^^^^^^^^^^^^

Теперь все вместе:

foo()
# NoMethodError: undefined method `foo'

self.foo
# NoMethodError: undefined method `foo'

foo
# NameError: undefined local variable or method `foo'

if false
  foo = 42
end

foo
# => nil

foo = :fortytwo

foo
# => :fortytwo

Проблема в этом конкретном случае заключается в том, что порядок, в котором выражения анализируются (и, следовательно, порядок, в котором определены переменные) не соответствует порядку, в котором выполняются выражения.

Сначала выполняется назначение, которое позволит предположить, что bar будет определен в теле. Но это не так, потому что тело сначала анализировалось, и поэтому я не знаю, был ли этот метод или переменная node вставлена ​​в дерево синтаксиса до того, как назначение было когда-либо замечено.

Однако, если этот node никогда не интерпретируется, то есть условие ложно, тогда ничего плохого не произойдет.

Ответ 3

Мой ответ основан на Ruby 2.1.2.

Добавление с помощью @Jörg W Mittag answer.

Другой часто путающий случай заключается в использовании модификатора if:

p a if a = 0.zero? # => NameError: undefined local variable or method `a'

Вместо того, чтобы печатать "true", вы получаете локальную переменную NameError, "undefined или метод" a ". Поскольку Ruby анализирует голый слева от if, если он еще не видел присваивания, предполагается, что вы хотите вызвать метод. Затем Ruby видит назначение a и предположим, что вы ссылаетесь на локальный метод.

Путаница происходит из выполнения выражения out-of-order выражения. Сначала назначается локальная переменная, затем вы пытаетесь вызвать несуществующий метод.

Основываясь на приведенном выше объяснении -

bar if bar = false

просто возвращает nil, поскольку выражение было оценено как false, тело кода, связанного с модификатором if, не будет выполнено. nil возвращается любым блоком в Ruby по умолчанию, когда нет явного значения по умолчанию.