Ruby conditionals subtlety?

Hi,

Anyone know why thse two forms of “unless” behave differently?

irb
irb(main):001:0> foo = true unless defined?(foo)
=> nil
irb(main):002:0> unless defined?(fooo) ; fooo = true ; end
=> true

thx

On Thu, Feb 18, 2010 at 12:49 PM, Farhad F. [email protected]
wrote:

thx

Posted via http://www.ruby-forum.com/.

From playing around it doesn’t look like unless is “behaving
differently”
Ruby has created a label for foo since it thinks it is going to be
assigning
it something (dynamic language)

For example

foo = true unless defined?(foo)
=> nil
unless defined?(fooo); fooo = true; end
=> true

foo
=> nil
fooo
=> true

foo = true unless defined?(foom)
=> true

foo
=> true

Basically it looks like foo appears to exist but it doesn’t really exist
yet
so the first unless “acts” oddly. This is very interesting though.
Perhaps
someone who knows what they are talking about can tell us why it appears
to
behave this way.


“Hey brother Christian with your high and mighty errand, Your actions
speak
so loud, I can’t hear a word you’re saying.”

-Greg Graffin (Bad Religion)

On Feb 18, 2010, at 11:49 , Farhad F. wrote:

Hi,

Anyone know why thse two forms of “unless” behave differently?

irb
irb(main):001:0> foo = true unless defined?(foo)
=> nil
irb(main):002:0> unless defined?(fooo) ; fooo = true ; end
=> true

oddity in the way the code is parsed:

% echo “foo = true unless defined?(foo)” | parse_tree_show
s(:if, s(:defined, s(:lvar, :foo)), nil, s(:lasgn, :foo, s(:true)))

% echo “unless defined?(fooo) ; fooo = true ; end” | parse_tree_show
s(:if, s(:defined, s(:call, nil, :fooo, s(:arglist))), nil, s(:lasgn,
:fooo, s(:true)))

Ryan D. wrote:

On Feb 18, 2010, at 11:49 , Farhad F. wrote:

Hi,

Anyone know why thse two forms of “unless” behave differently?

irb
irb(main):001:0> foo = true unless defined?(foo)
=> nil
irb(main):002:0> unless defined?(fooo) ; fooo = true ; end
=> true

oddity in the way the code is parsed:

% echo “foo = true unless defined?(foo)” | parse_tree_show
s(:if, s(:defined, s(:lvar, :foo)), nil, s(:lasgn, :foo, s(:true)))

% echo “unless defined?(fooo) ; fooo = true ; end” | parse_tree_show
s(:if, s(:defined, s(:call, nil, :fooo, s(:arglist))), nil, s(:lasgn,
:fooo, s(:true)))

Cool, any chance you could give a short description for those of us that
have never really thought about the parser or used parse_tree_show?

thx

On Feb 18, 2010, at 13:44 , Farhad F. wrote:

irb(main):002:0> unless defined?(fooo) ; fooo = true ; end

Cool, any chance you could give a short description for those of us that
have never really thought about the parser or used parse_tree_show?

Your latter code snippet treats “fooo” in defined? as a method call.
This is because the assignment inside the conditional hasn’t been parsed
yet, and hasn’t affected the lookup tables.

The former doesn’t have this problem because the body of the conditional
is parsed first.

Ryan D. wrote:

On Feb 18, 2010, at 13:44 , Farhad F. wrote:

irb(main):002:0> unless defined?(fooo) ; fooo = true ; end

Cool, any chance you could give a short description for those of us that
have never really thought about the parser or used parse_tree_show?

Your latter code snippet treats “fooo” in defined? as a method call.
This is because the assignment inside the conditional hasn’t been parsed
yet, and hasn’t affected the lookup tables.

The former doesn’t have this problem because the body of the conditional
is parsed first.

Thanks. To make sure I understand: In the first case,

foo = true unless defined?(foo)

the parser encounters ‘foo’, adds it to the symbol table, and then sees
the defined? call, so the net result is that foo is defined, but has nil
value. As such, if we’re ever going to use defined? as a conditional,
it should precede any other mention of the token (foo).

Is this correct?

On Feb 18, 2010, at 16:04 , Farhad F. wrote:

yet, and hasn’t affected the lookup tables.
value. As such, if we’re ever going to use defined? as a conditional,
it should precede any other mention of the token (foo).

No, it isn’t necessarily defined. AT PARSE TIME it sees the initial foo
and decides it is a local variable so it treats the foo in defined?(foo)
as a local variable. AT RUN TIME it evaluates the defined?(foo) and acts
accordingly.

On Feb 18, 2010, at 16:04 , Farhad F. wrote:

yet, and hasn’t affected the lookup tables.
value. As such, if we’re ever going to use defined? as a conditional,
it should precede any other mention of the token (foo).

I should also point out: almost all of this is of no consequence. You
almost never use defined? on a local variable like this. You usually use
it on a const, ivar, cvar, or global, and all of those are unambiguous.

Farhad F. wrote:

I should also point out: almost all of this is of no consequence. You
almost never use defined? on a local variable like this. You usually use
it on a const, ivar, cvar, or global, and all of those are unambiguous.

Thanks. I sometimes use it in Rails partials rendering, where I may
pass an optional local variable, but if it isn’t passed, I want to set
it to some default value.

BTW, I wonder,

In templates, local variables are created dynamically (e.g., the
programmer passes a hash of “variables” to the template engine). So how
can Ruby know, in the template code, that a “name” refers to a variable?
By default Ruby thinks names are method invocations and since there’s no
assignment, Ruby has no way to know these are variables…

On Feb 18, 2010, at 17:48 , Farhad F. wrote:

Thanks. I sometimes use it in Rails partials rendering, where I may
pass an optional local variable, but if it isn’t passed, I want to set
it to some default value. Perhaps there’s a better way of doing this…

var ||= default

Ryan D. wrote:

On Feb 18, 2010, at 16:04 , Farhad F. wrote:

yet, and hasn’t affected the lookup tables.
value. As such, if we’re ever going to use defined? as a conditional,
it should precede any other mention of the token (foo).

I should also point out: almost all of this is of no consequence. You
almost never use defined? on a local variable like this. You usually use
it on a const, ivar, cvar, or global, and all of those are unambiguous.

Thanks. I sometimes use it in Rails partials rendering, where I may
pass an optional local variable, but if it isn’t passed, I want to set
it to some default value. Perhaps there’s a better way of doing this…

Maybe it’s clearer like this:

if false
foo = 123
end
puts foo # nil

puts bar # undefined local variable or method ‘bar’

That is, for a bare word expression like ‘foo’ ruby has to decide
whether to parse it as a method call - as foo() or self.foo - or as a
local variable reference.

It makes the decision based on whether there has been a previous
assignment of the form “foo = …” parsed earlier in the code. This is
regardless of whether the code is actually executed, because we haven’t
started executing any of it yet.

“earlier” in the code is strictly left-to-right.

foo = 123 if not defined?(foo)

At the point of defined?, “foo = …” has already been seen, and so
bareword foo is known to be a local variable, and therefore the symbol
is ‘defined’ as a local variable at this point in the source code,
whether or not an assignment has actually been made.

On Thu, Feb 18, 2010 at 5:18 PM, Ryan D. [email protected]
wrote:

Your latter code snippet treats “fooo” in defined? as a method call. This is because the assignment inside the conditional hasn’t been parsed yet, and hasn’t affected the lookup tables.

The former doesn’t have this problem because the body of the conditional is parsed first.

And:

On Sat, Feb 20, 2010 at 5:28 AM, Brian C. [email protected]
wrote:

whether to parse it as a method call - as foo() or self.foo - or as a
local variable reference.

It makes the decision based on whether there has been a previous
assignment of the form “foo = …” parsed earlier in the code. This is
regardless of whether the code is actually executed, because we haven’t
started executing any of it yet.

I think I understand the gist of this – if the parser has seen foo
already, it is considered “defined” – but how does the distinction of
method vs. local variable matter in this context? As far as I can
tell, defined? works the same on local variable names and method
names.

Eric C. wrote:

I think I understand the gist of this – if the parser has seen foo
already, it is considered “defined” – but how does the distinction of
method vs. local variable matter in this context? As far as I can
tell, defined? works the same on local variable names and method
names.

irb(main):001:0> bar = 123
=> 123
irb(main):002:0> defined?(bar)
=> “local-variable”
irb(main):003:0> defined?(puts)
=> “method”
irb(main):004:0> defined?(baz)
=> nil

furthermore:

irb(main):005:0> puts = “Hello”
=> “Hello”
irb(main):006:0> defined?(puts)
=> “local-variable”

So, defined?(xxx) will return non-nil if xxx is a local variable (a
decision made at parse time), and if not then if the current object has
a method called :xxx (which is only known at run time). Hence

foo = 123
bar if defined?(foo)

is the same as

foo = 123
bar if “local-variable”

because foo is known at parse-time to be a local variable.

Regards,

Brian.

On Feb 18, 2010, at 23:06 , Albert S. wrote:

In templates, local variables are created dynamically (e.g., the
programmer passes a hash of “variables” to the template engine). So how
can Ruby know, in the template code, that a “name” refers to a variable?
By default Ruby thinks names are method invocations and since there’s no
assignment, Ruby has no way to know these are variables…

that’s a rails question. Please take it to a rubyonrails forum (or dig
up the code–but I really don’t recommend that).

On Sun, Feb 21, 2010 at 8:33 AM, Brian C. [email protected]
wrote:

=> “local-variable”
=> “local-variable”
foo = 123
bar if “local-variable”

because foo is known at parse-time to be a local variable.

Regards,

Ah, thanks – I wasn’t grokking the parse-time/runtime distinction in
this case. So:

def foo; 456; end unless defined? foo
puts foo

will print 456, because at parse time foo is undefined, even though it
gets made into a method name at runtime.

defined? does appear to be a rather odd beast though. You’d have to dig
hard through the source to explain the following (which I’m not going to
try):

defined?(1)
=> “expression”

defined?(1+2)
=> “method”

This is with:

RUBY_VERSION
=> “1.8.7”

RUBY_PATCHLEVEL
=> 174

Eric C. wrote:

So:

def foo; 456; end unless defined? foo
puts foo

will print 456, because at parse time foo is undefined, even though it
gets made into a method name at runtime.

Almost - perhaps I muddied things a bit. At parse time, in the above
code, foo is not ‘undefined’; it is known that it must be a method name,
because it’s not a local variable. But it’s not known whether there will
be a method called foo at the time this code is executed.

So, the result of defined?(foo) is decided at runtime. But if foo is a
local variable at that point in the source code (which is decided at
parse time), then you always know that the result of defined?(foo) will
be “local-variable”, since the parse tree contains “NODE_LVAR foo”; the
parser had already chosen foo to be a local variable, and this is a fact
which cannot be altered subsequently.

I don’t think the MRI interpreter actually optimises away defined?(LVAR)
to a constant at parse time as I might have implied, but in theory it
could.

For methods, the result is not known until runtime. Example:

2.times { puts defined?(foo); def foo; end }
nil
method
=> 2