Ruby conditionals subtlety?

F

Farhad Farzaneh

Hi,

Anyone know why thse two forms of "unless" behave differently?
irb(main):001:0> foo = true unless defined?(foo)
=> nil
irb(main):002:0> unless defined?(fooo) ; fooo = true ; end
=> true

thx
 
G

Glen Holcomb

Hi,

Anyone know why thse two forms of "unless" behave differently?

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

thx
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 assignin=
g
it something (dynamic language)

For example
=3D> true
=3D> true
=3D> true
=3D> true

Basically it looks like foo appears to exist but it doesn't really exist ye=
t
so the first unless "acts" oddly. This is very interesting though. Perhap=
s
someone who knows what they are talking about can tell us why it appears to
behave this way.

--=20
"Hey brother Christian with your high and mighty errand, Your actions speak
so loud, I can=92t hear a word you=92re saying."

-Greg Graffin (Bad Religion)
 
R

Ryan Davis

Hi,
=20
Anyone know why thse two forms of "unless" behave differently?
=20
irb(main):001:0> foo =3D true unless defined?(foo)
=3D> nil
irb(main):002:0> unless defined?(fooo) ; fooo =3D true ; end
=3D> true

oddity in the way the code is parsed:

% echo "foo =3D true unless defined?(foo)" | parse_tree_show=20
s:)if, s:)defined, s:)lvar, :foo)), nil, s:)lasgn, :foo, s:)true)))

% echo "unless defined?(fooo) ; fooo =3D true ; end" | parse_tree_show=20=

s:)if, s:)defined, s:)call, nil, :fooo, s:)arglist))), nil, s:)lasgn, =
:fooo, s:)true)))
 
F

Farhad Farzaneh

Ryan said:
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
 
R

Ryan Davis

=20
Cool, any chance you could give a short description for those of us = that=20
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.
 
F

Farhad Farzaneh

Ryan said:
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?
 
R

Ryan Davis

=20
Thanks. To make sure I understand: In the first case,
=20
=20
the parser encounters 'foo', adds it to the symbol table, and then = sees=20
the defined? call, so the net result is that foo is defined, but has = nil=20
value. As such, if we're ever going to use defined? as a conditional,=20=
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.
 
R

Ryan Davis

=20
Thanks. To make sure I understand: In the first case,
=20
=20
the parser encounters 'foo', adds it to the symbol table, and then = sees=20
the defined? call, so the net result is that foo is defined, but has = nil=20
value. As such, if we're ever going to use defined? as a conditional,=20=
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.
 
F

Farhad Farzaneh

Ryan said:
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...
 
A

Albert Schlef

Farhad said:
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...
 
R

Ryan Davis

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
 
R

Ryan Davis

=20
BTW, I wonder,
=20
In templates, local variables are created dynamically (e.g., the=20
programmer passes a hash of "variables" to the template engine). So = how=20
can Ruby know, in the template code, that a "name" refers to a = variable?=20
By default Ruby thinks names are method invocations and since there's = no=20
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).
 
B

Brian Candler

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.
 
E

Eric Christopherson

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, a=
nd hasn't affected the lookup tables.
The former doesn't have this problem because the body of the conditional =
is parsed first.

And:

Maybe it's clearer like this:

=A0if false
=A0 =A0foo =3D 123
=A0end
=A0puts foo =A0 =A0 =A0# nil

=A0puts bar =A0 =A0 =A0# 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 =3D ..." 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.
 
B

Brian Candler

Eric said:
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.
 
E

Eric Christopherson

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,

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.
 
B

Brian Candler

Eric said:
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:
nil
method
=> 2
 
B

Brian Candler

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):
=> "method"

This is with:
=> 174
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top