On Apr 4, 2007, at 3:25 PM, François Montel wrote:
Why is it that the nil? method can sometimes be called on an object
doesn’t exist, and other times will return an error. Under what
circumstances can you call the nil? method on a non-existent object?
It is an artifact of the way the Ruby parser works and the syntactical
ambiguity of a method call with no arguments and local variables.
puts x = 1 if x.nil?
Here, the Ruby parser ‘sees’ the expression: ‘x = 1’ and concludes that
x must be a local variable. It sees the expression and makes the
determination about ‘x’ before it sees the method call: ‘x.nil?’.
Once it has made that determination is considers ‘x.nil?’ as a method
call against the local variable ‘x’ and not as a method call for ‘x’
relative to ‘self’.
The net effect is that the local variable ‘x’ is willed into existence
by the parser (and defaults to nil). Since ‘x.nil?’
evaluates to true, ‘puts x = 1’ is executed changing the value of x
to 1, which gets printed.
Consider a situation where x was a method call with an argument:
puts x = 1 if x(‘foo’).nil?
In this case the parser sees no ambiguity, “x(‘foo’)” must be a method
call even though it already saw ‘x = 1’ as a local variable assignment.
puts ‘z is not defined’ if z.nil?
=> NameError: undefined local variable or method ‘z’
Here the parser hasn’t seen ‘z’ until it comes across ‘z.nil?’. It
makes the determination that z must be a method call. When the parsing
is complete and the statement is executed, Ruby tries to call ‘z’ in
the current context and finds that there really isn’t a method
called ‘z’ and you get the Name Error.
Note that the parser can’t determine if the method z exists or not.
It is only at the time of execution that that can be determined:
def z; ‘z exists!’; end
puts z rescue “z isn’t defined” # => “z isn’t defined”
puts z # => “z exists!”
Here is another example
if w # parser sees this as a method call, NameError
# exception is raised.
puts w = 42 # parser sees this as local variable assignment
# but it never executes
Now consider (in a new session of ruby or irb):
puts w = 42 if w # parser sees w as a local variable and initializes
# w to nil, puts never executes