Почему Ruby сохраняет оценку кода после создания исключения NameError?
простой код, который я не могу объяснить себе:
puts a if a = 1
в результате
warning: found = in conditional, should be ==
NameError: undefined local variable or method 'a' for main:Object
хотя, теперь при проверке a
мы можем видеть, что он был определен:
a #=> 1
почему a
назначается 1
несмотря на исключение?
С docs:
путаница начинается с неупорядоченное выполнение выражения. Сначала местный переменная назначается-затем вы пытаетесь вызвать a несуществующий метод [
a
].
эта часть все еще запутывает-почему интерпретатор не обнаруживает уже определен локальная переменная a
и все еще пытается вызвать" несуществующий " метод? Если он также не проверяет локальные переменные, найдите определенную локальную переменную a
и печать 1
?
2 ответов
давайте посмотрим на абстрактное дерево синтаксиса Ruby для модификатора if
:
$ ruby --dump=parsetree -e 'puts a if a = 1'
# @ NODE_SCOPE (line: 1, code_range: (1,0)-(1,15))
# +- nd_tbl: :a
# +- nd_args:
# | (null node)
# +- nd_body:
# @ NODE_PRELUDE (line: 1, code_range: (1,0)-(1,15))
# +- nd_head:
# | (null node)
# +- nd_body:
# | @ NODE_IF (line: 1, code_range: (1,0)-(1,15))
# | +- nd_cond:
# | | @ NODE_DASGN_CURR (line: 1, code_range: (1,10)-(1,15))
# | | +- nd_vid: :a
# | | +- nd_value:
# | | @ NODE_LIT (line: 1, code_range: (1,14)-(1,15))
# | | +- nd_lit: 1
# | +- nd_body:
# | | @ NODE_FCALL (line: 1, code_range: (1,0)-(1,6))
# | | +- nd_mid: :puts
# | | +- nd_args:
# | | @ NODE_ARRAY (line: 1, code_range: (1,5)-(1,6))
# | | +- nd_alen: 1
# | | +- nd_head:
# | | | @ NODE_VCALL (line: 1, code_range: (1,5)-(1,6))
# | | | +- nd_mid: :a
# | | +- nd_next:
# | | (null node)
# | +- nd_else:
# | (null node)
# +- nd_compile_option:
# +- coverage_enabled: false
и if
:
$ ruby --dump=parsetree -e 'if a = 1 then puts a end'
# @ NODE_SCOPE (line: 1, code_range: (1,0)-(1,24))
# +- nd_tbl: :a
# +- nd_args:
# | (null node)
# +- nd_body:
# @ NODE_PRELUDE (line: 1, code_range: (1,0)-(1,24))
# +- nd_head:
# | (null node)
# +- nd_body:
# | @ NODE_IF (line: 1, code_range: (1,0)-(1,24))
# | +- nd_cond:
# | | @ NODE_DASGN_CURR (line: 1, code_range: (1,3)-(1,8))
# | | +- nd_vid: :a
# | | +- nd_value:
# | | @ NODE_LIT (line: 1, code_range: (1,7)-(1,8))
# | | +- nd_lit: 1
# | +- nd_body:
# | | @ NODE_FCALL (line: 1, code_range: (1,14)-(1,20))
# | | +- nd_mid: :puts
# | | +- nd_args:
# | | @ NODE_ARRAY (line: 1, code_range: (1,19)-(1,20))
# | | +- nd_alen: 1
# | | +- nd_head:
# | | | @ NODE_DVAR (line: 1, code_range: (1,19)-(1,20))
# | | | +- nd_vid: :a
# | | +- nd_next:
# | | (null node)
# | +- nd_else:
# | (null node)
# +- nd_compile_option:
# +- coverage_enabled: false
единственное отличие-аргумент метода для puts
:
# | | | @ NODE_VCALL (line: 1, code_range: (1,5)-(1,6))
# | | | +- nd_mid: :a
vs:
# | | | @ NODE_DVAR (line: 1, code_range: (1,19)-(1,20))
# | | | +- nd_vid: :a
с модификатором if
, парсер обрабатывает a
как вызов метода и создает NODE_VCALL
. Это указывает интерпретатору сделать вызов метода (хотя там и локальная переменная a
), в результате NameError
. (потому что нет метода a
)
стандартные if
, парсер обрабатывает a
как локальная переменная и создает NODE_DVAR
. Это указывает интерпретатору искать локальную переменную, которая работает так, как ожидалось.
как вы можете видеть, Ruby распознает локальные переменные на уровне парсера. Вот почему в документации говорится: (курсив добавлен)
модификатор и стандартные версии [...] не являются точными преобразования друг друга из-за разбора заказа.
Ruby анализирует код слева направо. Локальные переменные определяются при анализе первого назначения им. At puts a
нет задание a
был проанализирован еще, таким образом, локальная переменная a
еще не существует, и Ruby предполагает a
- это вызов метода. Локальная переменная существует только в право и ниже задание.
во время выполнения Ruby должен оценить условие, чтобы выяснить, следует ли выполнять puts
, так что a
инициализируется к 1
.
вы, похоже, выполняете этот код в каком-то REPL. Обычно repls спасает исключения вместо завершения, поэтому ваш код продолжает выполняться вместо завершения, и поскольку мы теперь ниже назначения, переменная определена, и поскольку назначение было выполнено, переменная инициализируется.
если различие между определение и инициализации of переменная вам непонятна, задумайтесь над этим:
foo
# NameError
if false
foo = 42
end
foo
#=> nil
foo = :bar
foo
#=> :bar