2015-03-31 2 views
8

Итак, мы имеем код:Рубины "определены?" оператор работает неправильно?

class Foo 
    def bar 
    puts "Before existent: #{(defined? some_variable)}" 
    puts "Before not_existent: #{(defined? nonexistent_variable)}" 

    raise "error" 

    some_variable = 42 
    rescue 
    puts "exception" 
    ensure 
    puts "Ensure existent: #{(defined? some_variable)}" 
    puts "Ensure not_existent: #{(defined? nonexistent_variable)}" 
    end 
end 

И вызвать его из IRB:

> Foo.new.bar 

И, что вернется:

Before existent: 
Before not_existent: 
exception 
Ensure existent: local-variable 
Ensure not_existent: 
=> nil 

А теперь вопрос - почему? Мы собрали исключение до, чем some_variable. Почему это работает так? Почему some_variable определен в блоке обеспечения? (Кстати, она определяется как ноль)

UPDATE: Спасибо @Max для ответа, но если мы изменим код, чтобы использовать переменную экземпляра:

class Foo 
    def bar 
    puts "Before existent: #{(defined? @some_variable)}" 
    puts "Before not_existent: #{(defined? @nonexistent_variable)}" 

    raise "error" 

    @some_variable = 42 
    ensure 
    puts "Ensure existent: #{(defined? @some_variable)}" 
    puts "Ensure not_existent: #{(defined? @nonexistent_variable)}" 
    end 
end 

Он работает, как ожидалось:

Before existent: 
Before not_existent: 
Ensure existent: 
Ensure not_existent: 

Почему?

+0

Ссылки на неопределенные переменные экземпляра (и глобальные) обрабатываются по-разному, чем ссылки на неопределенные локальные (и классные) переменные. Например, 'puts @a # => nil', тогда как' помещает #NameError: неопределенную локальную переменную или метод 'a 'для main: Object'. –

ответ

5

Первое, что нужно заметить, это то, что defined? является ключевым словом , а не способом. Это значит, что во время компиляции, когда построено синтаксическое дерево (if, return, next и т. Д.), Синтаксический анализатор обрабатывается синтаксисом, а не динамически просматривается во время выполнения.

Именно поэтому defined? может обрабатывать выражения, которые обычно поднимают ошибку: defined?(what is this even) #=> nil, потому что анализатор может исключить свой аргумент из обычного процесса оценки.

Действительно запутанный бит в том, что, хотя это ключевое слово, его поведение равно still determined at runtime. Он использует парсер магию, чтобы определить, является ли ее аргумент является переменной экземпляра, константа, метод и т.д. , но затем звонки обычные методы Руби, чтобы определить, были ли определены эти конкретные типы во время выполнения:

// ... 
case DEFINED_GVAR: 
if (rb_gvar_defined(rb_global_entry(SYM2ID(obj)))) { 
    expr_type = DEFINED_GVAR; 
} 
break; 
case DEFINED_CVAR: 
// ... 
if (rb_cvar_defined(klass, SYM2ID(obj))) { 
    expr_type = DEFINED_CVAR; 
} 
break; 
case DEFINED_CONST: 
// ... 
if (vm_get_ev_const(th, klass, SYM2ID(obj), 1)) { 
    expr_type = DEFINED_CONST; 
} 
break; 
// ... 

Это rb_cvar_defined функции это то же самое, что называется, например, Module#class_variable_defined?.

So defined? странный. Очень странно. Его поведение может сильно варьироваться в зависимости от его аргументации, и я бы даже не поставил на него одно и то же в разных реализациях Ruby. Исходя из этого, я бы рекомендовал не использовать его и вместо этого использовать методы Ruby's *_defined?, где это возможно.

+0

Ничего себе. Это странно. У вас есть какие-то документы или подробности об этом? –

+0

@ mr.The updated – Max

+0

Я обновляю вопрос –

Смежные вопросы