9

I stumbled upon a strange behavior in ruby regarding variable definition (and lost a box of donuts on the way):

irb(main):001:0> if false
irb(main):002:1>   a = 1
irb(main):003:1> end
=> nil
irb(main):005:0> a.nil?
=> true
irb(main):006:0> b.nil?
NameError: undefined local variable or method `b' for main:Object
    from (irb):6
    from /Users/jlh/.rbenv/versions/2.1.5/bin/irb:11:in `<main>'

Why isn't a.nil? throwing undefined local variable? Take a look at python, for instance (just wanted to compare it to an interpreted language):

>>> if False:
...     a = 1
... 
>>> print a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

In a compiled language this wouldn't even compile.

  • Does this mean that ruby is keeping a reference to that variable even if it hasn't gone through that piece of code?
  • If so, how deep are the ifs/else considered for variable definition?

I really can't believe this is the expected behavior in ruby. And it is not irb-specific, running it in a ruby/rails code block gives the same result.

jlhonora
  • 10,179
  • 10
  • 46
  • 70
  • 3
    It's because Ruby sets local variable scopes on parse time. `a` is used as local variable in this scope, so Ruby treats it as local variable (even though it's never initialized). – Marek Lipka Feb 27 '15 at 12:37
  • @MarekLipka interesting. I sensed it could be parsing related, that's why I used the AST tag for this question – jlhonora Feb 27 '15 at 12:49
  • 4
    Read it from http://ruby-doc.org/core-2.1.2/doc/syntax/assignment_rdoc.html#label-Local+Variables+and+Methods – Arup Rakshit Feb 27 '15 at 13:11

2 Answers2

6

In Ruby, there is an ambiguity between referencing a local variable and a message send to the implicit receiver without an argument list. That means

foo

can either mean "dereference a local variable" or "send message foo to self without arguments", i.e. it could either be equivalent to

binding.local_variable_get(:foo)

or

self.foo()
# or
public_send(:foo)

This ambiguity is resolved at parse time. When the parser encounters an assignment to foo, it will, from that point on, treat foo as a local variable, regardless of whether or not the assignment actually gets executed. (That's something the parser cannot determine statically, after all. Just think about if rand > 0.5 then foo = 42 end.)

In a compiled language this wouldn't even compile.

There is no such thing as a compiled language. Compilation and interpretation are traits of the compiler or interpreter (duh!) not the language. Languages are neither compiled nor interpreted. They just are.

Every language can be implemented with a compiler and every language can be implemented with an interpreter. Most languages have both compiled and interpreted implementations (e.g. C has GCC and Clang, which are compilers, and Cint and Cling, which are interpreters, Haskell has GHC, which is a compiler, and Hugs, which is an interpreter).

Many modern language implementations have both in the same implementation, either in different phases (e.g. YARV and MRuby compile Ruby sourcecode to internal bytecode, and then interpret that bytecode), or in a mixed-mode engine (e.g. the HotSpot JVM both interprets and compiles JVM bytecode, depending on which makes more sense), or both (e.g. Rubinius compiles Ruby sourcecode to Rubinius bytecode in the first phase, and then both compiles that bytecode to native code and interprets it, depending on what makes more sense).

In fact, all currently existing Ruby implementations are compiled: YARV and MRuby compile to their own internal bytecode formats, Rubinius, MacRuby, MagLev and Topaz compile to their own internal bytecode formats, then compile that to native code, JRuby compiles to JVM bytecode (which the JVM may or may not compile further), IronRuby compiles to CIL bytecode (which the VES may or may not compile further).

The fact that Ruby behaves this way is because the language specification says so. Not because Ruby is "interpreted", because, actually, it is not. The only purely interpreted implementation of Ruby was MRI and very early versions of JRuby, and both have long since been retired.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Good call on the compilation argument, _programming languages just are_ is a phrase I'll probably never forget – jlhonora Feb 27 '15 at 17:07
  • I can't take credit for it, I heard it in an interview with [Shriram Krishnamurthi](http://cs.brown.edu/~sk/), I believe it was [this one](http://channel9.msdn.com/Shows/Going+Deep/Expert-to-Expert-Web-Programming-with-Flapjax). – Jörg W Mittag Feb 27 '15 at 17:45
1

I might be wrong on this, but Ruby defines scopes for your variables. You have the global scope, which is $

Then you have the local scope of your running script, which is the one you've demonstrated in the question. You can define the variable inside a method, but it will still be available in the local scope of the running script.

source: http://ruby-doc.org//docs/ruby-doc-bundle/UsersGuide/rg/localvars.html

Here it's illustrated that local variables do not have the initial value of nil, but once defined take whatever value they have, regardless of being defined.

2.1.5 :001 > p 1
1
 => 1 
2.1.5 :002 > p a
NameError: undefined local variable or method `a' for main:Object
    from (irb):2
    from /Users/deh0002a/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'
2.1.5 :003 > if false
2.1.5 :004?>   a = 2
2.1.5 :005?>   else
2.1.5 :006 >     a = 3
2.1.5 :007?>   end
 => 3 
2.1.5 :008 > p a
3
 => 3 
2.1.5 :009 > p$a
nil
 => nil 
2.1.5 :010 > p @a
nil
 => nil 
2.1.5 :011 > 

The difference again is with Global and Instance variables. Even when they have not been defined, they automatically take the value of nil.

codingbunny
  • 3,989
  • 6
  • 29
  • 54