Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Ruby, why after starting irb, foo.nil? says undefined error, and @foo.nil? gives "true", and @@wah.nil? gives error again?

Same in Ruby 1.8.7 and 1.9.2:

$ irb

ruby-1.8.7-p302 > foo.nil?
NameError: undefined local variable or method `foo' for #<Object:0x3794c>
    from (irb):1

ruby-1.8.7-p302 > @bar.nil?
 => true 

ruby-1.8.7-p302 > @@wah.nil?
NameError: uninitialized class variable @@wah in Object
    from (irb):3

why the instance variable treated differently than a local and class variable?

like image 515
nonopolarity Avatar asked Oct 26 '10 07:10

nonopolarity


1 Answers

In Ruby, most uninitialized or even non-existing variables evaluate to nil. This is true for local variables, instance variables and global variables:

defined? foo       #=> nil
local_variables    #=> []
if false
  foo = 42
end
defined? foo       #=> 'local-variable'
local_variables    #=> [:foo]
foo                #=> nil
foo.nil?           #=> true

defined? @bar      #=> nil
instance_variables #=> []
@bar               #=> nil
@bar.nil?          #=> true
# warning: instance variable @bar not initialized

defined? $baz      #=> nil
$baz               #=> nil
# warning: global variable `$baz' not initialized
$baz.nil?          #=> true
# warning: global variable `$baz' not initialized

It is, however, not true for class hierarchy variables and constants:

defined? @@wah     #=> nil
@@wah
# NameError: uninitialized class variable @@wah in Object

defined? QUUX      #=> nil
QUUX
# NameError: uninitialized constant Object::QUUX

This is a red herring:

defined? fnord     #=> nil
local_variables    #=> []
fnord
# NameError: undefined local variable or method `fnord' for main:Object

The reason why you get an error here is not that unitialized local variables don't evaluate to nil, it is that fnord is ambiguous: it could be either an argument-less message send to the default receiver (i.e. equivalent to self.fnord()) or an access to the local variable fnord.

In order to disambiguate that, you need to add a receiver or an argument list (even if empty) to tell Ruby that it is a message send:

self.fnord
# NoMethodError: undefined method `fnord' for main:Object
fnord()
# NoMethodError: undefined method `fnord' for main:Object

or make sure that the parser (not the evaluator) parses (not executes) an assignment before the usage, to tell Ruby that it is a local variable:

if false
  fnord = 42
end
fnord              #=> nil

why the instance variable treated differently than a local and class variable?

It's not, actually. It's treated the same as a local variable. The class hierarchy variable is the one that behaves differently, local variables, instance variables and global variables all behave the same.

is there other reasons … can't class variables behave like that too?

I don't know. For instance variables it is very convenient, since unlike in Java, for example, where instance variables are declared in the class definition und thus always exist for every instance of the class, in Ruby, instance variables aren't declared anywhere. They just magically spring into existence, as soon as they are assigned. Since instance variables aren't necessarily guaranteed to exist, writing methods that use instance variables would be a pain if they threw exceptions.

Why class hierarchy variables are different, I have no idea. Maybe it's because nobody uses them anyway, or because they generally tend to be initialized in the class body and simply aren't accessed when they are not initialized.

like image 163
Jörg W Mittag Avatar answered Nov 14 '22 09:11

Jörg W Mittag