0

This question deals with Ruby 1.9.3 specifically:

p defined?(a)
p binding.eval "defined?(a)"
b = lambda { |x| x }
p b.binding.eval "defined?(a)" # This prints "local-variable"
p defined?(a) # This prints nil!
a = 2
p defined?(a)
p b.binding.eval "defined?(a)"

What confuses me is line four. I'm not sure why this prints "local-variable" rather than nil. This seems to imply that lambda is somehow "looking farther." (I figure defined? being an operator has something to do with this.)

Also, although the binding says that it is defined, attempting to use it like so:

p b.binding.eval "a"

before the assignment on line 6 results in a NameError.

EDIT: I have tested this on

  • 1.9.3-362
  • 1.9.3-374
  • 2.0.0-preview2

I get the same behavior in all of the cases.

Kristopher Micinski
  • 7,572
  • 3
  • 29
  • 34

3 Answers3

2

What happens is that just calling defined? happens at parse-time, and since no variable a exists yet, it returns nil as expected. However, when you eval the defined? call, it’s deferred to runtime. But defined? is still lexically scoped, and since the entire file has already been parsed and compiled, the variable a does exist, because it was created at parse-time, which is now complete.

We can show this in a simplified example:

defined? a         #=> nil
eval 'defined? a'  #=> "local-variable"
a = 2
defined? a         #=> "local-variable"

But if we don’t define a at all:

defined? a         #=> nil
eval 'defined? a'  #=> nil

As you can see, it doesn’t have anything to do with binding, and instead simply has to do with eval deferring the evaluation of the defined? till after the entire file has been parsed.

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
0

It's actually line six that is the source of the mystery. Ruby creates objects in conditionals even when the code is not executed. If a variable is assigned, it is implicitly declared. This blog post has a quick explanation

I found this a little surprising myself. As for your code, in a new IRB session, you should see 'nil' as the output of line four, but if you run the code more than once, your variable is already there (in the form of a Nil object) and you'll get the perplexing news that a local variable exists. YMMV.

LazyMonkey
  • 517
  • 5
  • 8
  • that's not really it though. I knew that Ruby had the `if` behavior, and my question didn't really deal with that (what I'm talking about happens in the `ruby` interpreter only, `irb` will parse differently). I removed the `if` to clarify! – Kristopher Micinski Feb 07 '13 at 22:46
0

It's a feature of Ruby source parser. If there does exist variable assignment in Ruby source code it's initialized to nil (so it gets defined) even if never called during program's execution. There is the assignment on line 6 to the local variable a (a = 2). It does not matter it is defined after your assignment on line 3 as it gets initialized to nil by ruby parser before a source is even executed. As lambda does not introduce a new scope, it knows about a variable so you get unsurprisingly "local-variable" result (a == nil). See this answer for some details.

The different behaviour between running in irb or passing to ruby interpreter is directly related to the above paragraph. irb evaluates expressions line-by-line (REPL), ruby parses the whole source before its execution.

Community
  • 1
  • 1
David Unric
  • 7,421
  • 1
  • 37
  • 65
  • I understand Ruby's parsing this in this manner, but that still doesn't answer my question: if that's the case then why the mismatch between the lambda's binding and just using defined on size 5 and 6. Those two have the same scope (since binding will just capture the scope at the time the lambda was created). I don't understand the mismatch, can you explain? – Kristopher Micinski Feb 08 '13 at 01:00