why must I initialize this variable?

M

matt neuburg

Here's a simple variable initialization / scope question. In the
following code:

h = {:test => "cool"}
k = :test
result = nil
puts "#{result}" if (result = h[k])

....if I omit the third line (initializing "result"), I get an error.
Why? Intuitively I would have expected the second part of the 4th line
to create / initialize "result".

Another way of understanding my confusion is to notice that this *does*
work:

h = {:test => "cool"}
k = :test
if (result = h[k]) then puts "#{result}" end

So evidently I'm asking about the difference between the last line of
the first example and the last line of the second example. Intuitively I
would have expected these lines to be absolutely equivalent, but clearly
they are not. I'd like to understand the difference rigorously. Thx! m.
 
T

Thomas Wieczorek

Hello!

Here's a simple variable initialization / scope question. In the
following code:

First of all I think that you confuse assignment(=) with
comparison(==). Every operation in Ruby returns a value.
In assignment the right hand side is returned
h = {:test => "cool"}
#returns {:test => "cool"}

In comparison, either true or false are returned. An assignment
returns true if you don't assign _nil_ or _false_.
if foo = "bar"
puts foo
end
(irb):16: warning: found = in conditional, should be ==
As you see, irb or running Ruby with the '-w' will give you a warning,
that you used an assignment in a
h = {:test => "cool"}
k = :test
result = nil
puts "#{result}" if (result = h[k])

I am not sure if I am right: Here it tried to get the value of result
first, if it doesn't exist, an exception occurs. After looking up
result, it tests the if-clause.
...if I omit the third line (initializing "result"), I get an error.
Why? Intuitively I would have expected the second part of the 4th line
to create / initialize "result".

Another way of understanding my confusion is to notice that this *does*
work:

h = {:test => "cool"}
k = :test
if (result = h[k]) then puts "#{result}" end

Here, you assign, the assignment returns "cool", which is true and
then you print out the existing variable.

I hope you understand what I mean.

Regards,
Thomas
 
J

Justin Collins

matt said:
Here's a simple variable initialization / scope question. In the
following code:

h = {:test => "cool"}
k = :test
result = nil
puts "#{result}" if (result = h[k])

...if I omit the third line (initializing "result"), I get an error.
Why? Intuitively I would have expected the second part of the 4th line
to create / initialize "result".

Another way of understanding my confusion is to notice that this *does*
work:

h = {:test => "cool"}
k = :test
if (result = h[k]) then puts "#{result}" end

So evidently I'm asking about the difference between the last line of
the first example and the last line of the second example. Intuitively I
would have expected these lines to be absolutely equivalent, but clearly
they are not. I'd like to understand the difference rigorously. Thx! m


I _believe_ this is because the left-hand side is parsed first, as Ruby
works left to right.

You will notice that the variable _does_ get initialized:

irb(main):001:0> puts "#{hi}" if false
=> nil
irb(main):002:0> puts "#{hi}" if hi = "hello"
(irb):2: warning: found = in conditional, should be ==
NameError: undefined local variable or method `hi' for main:Object
from (irb):2
from :0
irb(main):003:0> hi
=> "hello"

But at the time (before evaluating the conditional clause) the variable
does not exist. The code which is run when the conditional succeeds is
what was parsed prior to evaluating the conditional.

At least, that is my understanding. Of course, best not to use
assignment as a conditional as it leads to confusion.

-Justin
 
M

matt neuburg

Justin Collins said:
irb(main):001:0> puts "#{hi}" if false
=> nil
irb(main):002:0> puts "#{hi}" if hi = "hello"
NameError: undefined local variable or method `hi' for main:Object
from (irb):2
from :0

Even weirder! Why does it work in the first case but not in the second.
If you're going to say that 'hi' is undefined, surely it was undefined
in line 001. Yet line 001 raised no error.

So what's wrong with 002? It cannot be the 'puts "#{hi}"' part - you
just proved, with 001, that that part's okay. So apparently it is the
assignment part. Evidently a tailgating "if" refuses to auto-instantiate
a variable. That is what I'm attempting to get clear on. Is that a bug?
Is it expected behavior? m.
 
M

matt neuburg

Thomas Wieczorek said:
Hello!



First of all I think that you confuse assignment(=) with
comparison(==).

No, I don't. The assignment is the point of the example. Possibly I
should have used double-parens around the assignment to make that clear
(a common C convention).

m.
 
J

Justin Collins

matt said:
Even weirder! Why does it work in the first case but not in the second.
If you're going to say that 'hi' is undefined, surely it was undefined
in line 001. Yet line 001 raised no error.

So what's wrong with 002? It cannot be the 'puts "#{hi}"' part - you
just proved, with 001, that that part's okay. So apparently it is the
assignment part. Evidently a tailgating "if" refuses to auto-instantiate
a variable. That is what I'm attempting to get clear on. Is that a bug?
Is it expected behavior? m.

Again, I am not 100% on this (interpreter implementors would know
better) but it's because the first line does not actually evaluate the
left hand side.

In the second line, the left hand side is parsed and set aside,
something like how a block is, (someone please correct me on this if I
am wrong) pending the evaluation of the conditional:

irb(main):001:0> a = lambda { puts "#{hi}" }
=> #<Proc:0xb7db81a4@(irb):1>
irb(main):002:0> a.call if false
=> nil
irb(main):003:0> a.call if true
NameError: undefined local variable or method `hi' for main:Object
from (irb):1
from (irb):3:in `call'
from (irb):3
from :0
irb(main):004:0> a.call if hi = "hello"
(irb):4: warning: found = in conditional, should be ==
NameError: undefined local variable or method `hi' for main:Object
from (irb):1
from (irb):4:in `call'
from (irb):4
from :0
irb(main):005:0> hi
=> "hello"

So, I guess it is something of a scope issue.

-Justin
 
C

Curt Sampson

puts "#{result}" if (result = h[k])

...I get an error...
Another way of understanding my confusion is to notice that this *does*
work:

if (result = h[k]) then puts "#{result}" end

This is just one of those little annoyances in Ruby. You'll
find things like this all over the place.

You may or may not get used to it. I never have, but I live with it
while waiting for the perfect language to come along.

cjs
 
J

Jesús Gabriel y Galán

Even weirder! Why does it work in the first case but not in the second.
If you're going to say that 'hi' is undefined, surely it was undefined
in line 001. Yet line 001 raised no error.

So what's wrong with 002? It cannot be the 'puts "#{hi}"' part - you
just proved, with 001, that that part's okay. So apparently it is the
assignment part. Evidently a tailgating "if" refuses to auto-instantiate
a variable. That is what I'm attempting to get clear on. Is that a bug?
Is it expected behavior? m.

Because of how the parser works. It makes a first pass, and one of the
things it does is decide wheter tokens are methods or variables. The
way it does that is assuming everything is a method and if it finds an
assignment, from that point on, it treats it as a variable. The trick
is in the "from that point on", since for the parser it's just left to
right as it finds the code, it doesn't take into account evaluation
order (please anyone correct me if I'm wrong). Check this:

irb(main):001:0> def hi; "hi"; end
=> nil
irb(main):002:0> puts "#{hi}" if (hi = "hello")
(irb):2: warning: found = in conditional, should be ==
hi
=> nil
irb(main):003:0> puts hi
hello
=> nil

As you can see, I define a method hi. The parser will treat the first
hi in line 002 as a method, since it hasn't seen an assignment yet.
That's why line 002 outputs "hi", because the first reference has been
marked by the parser as a method call. After the hi = "hello", though,
hi is treated as a variable. That's why line 003 outputs "hello".

Hope this makes it a bit clearer, although I agree that this looks confusing.

Jesus.
 
7

7stud --

Jesús Gabriel y Galán said:
Because of how the parser works. It makes a first pass, and one of the
things it does is decide wheter tokens are methods or variables. The
way it does that is assuming everything is a method and if it finds an
assignment, from that point on, it treats it as a variable. The trick
is in the "from that point on", since for the parser it's just left to
right as it finds the code, it doesn't take into account evaluation
order (please anyone correct me if I'm wrong). Check this:

irb(main):001:0> def hi; "hi"; end
=> nil
irb(main):002:0> puts "#{hi}" if (hi = "hello")
(irb):2: warning: found = in conditional, should be ==
hi
=> nil
irb(main):003:0> puts hi
hello
=> nil

As you can see, I define a method hi. The parser will treat the first
hi in line 002 as a method, since it hasn't seen an assignment yet.
That's why line 002 outputs "hi", because the first reference has been
marked by the parser as a method call. After the hi = "hello", though,
hi is treated as a variable. That's why line 003 outputs "hello".

Hope this makes it a bit clearer, although I agree that this looks
confusing.

Jesus.

So the parser marks hi as a method before the code hi="hello" is parsed,
and thereafter hi is marked as a variable? Then execution evaluates
hi="hello" and thereafter execution moves *backwards* to execute puts
hi? And in order to evaluate hi in the puts statement, execution
checks the parser's values, and at that point in the code hi is a
method, so the hi method executes and the return value is output?
 
J

Jesús Gabriel y Galán

So the parser marks hi as a method before the code hi=3D"hello" is parsed= ,
and thereafter hi is marked as a variable? Then execution evaluates
hi=3D"hello" and thereafter execution moves *backwards* to execute puts
hi? And in order to evaluate hi in the puts statement, execution
checks the parser's values, and at that point in the code hi is a
method, so the hi method executes and the return value is output?

Sorry, I don't have enough knowledge to answer your question. What I
explained and showed above is my understanding of how this issue works
from the functional point of view, but I have no idea of how the
parser and the execution internals are in this regard. Maybe some
other person might explain it.

Jesus.
 
G

Gary Wright

So the parser marks hi as a method before the code hi="hello" is
parsed,
and thereafter hi is marked as a variable? Then execution evaluates
hi="hello" and thereafter execution moves *backwards* to execute puts
hi?

Execution isn't moving backwards. You have to think of it as a two
step process, parsing then execution. The parser is going to prepare:

puts "#{hi}" if (hi = "hello")
puts hi

is such a way that the execution step sees:

if (hi = "hello")
puts "#{hi}"
end
puts hi

So during execution, nothing is going backwards. But the parser has
already tagged various parts of the code such that the 'hi' token in
the conditional is an local variable assignment from a literal (thus
the warning), the 'hi' token in the then clause as a method call, and
the 'hi' token on the final puts as a local variable.

These three issues
1) 0-argument method calls and local variables
are syntactically identical
2) local variables don't have to be declared.
3) Ruby's if/unless modifiers imply execution
order that is backwards from textual order

when considered separately are quite nice, but when combined, have
some awkward corner cases. You could get rid of the problem by:

a) forcing all 0-arg method calls to be self.method or method()
b) requiring temporary variables to be declared
c) eliminating if/unless modifiers
d) implementing more elaborate multi-pass parser
 
F

Frederick Cheung

puts "#{result}" if (result = h[k])

...I get an error...
Another way of understanding my confusion is to notice that this
*does*
work:

if (result = h[k]) then puts "#{result}" end

This is just one of those little annoyances in Ruby. You'll
find things like this all over the place.

Another corner case is

foo #=> NameError: undefined local variable or method `foo' for
main:Object
if false then foo=1;end
foo #=> nil

Fred
 
7

7stud --

Gary said:
Execution isn't moving backwards. You have to think of it as a two
step process, parsing then execution. The parser is going to prepare:

puts "#{hi}" if (hi = "hello")
puts hi

is such a way that the execution step sees:

if (hi = "hello")
puts "#{hi}"
end
puts hi

So during execution, nothing is going backwards. But the parser has
already tagged various parts of the code such that the 'hi' token in
the conditional is an local variable assignment from a literal (thus
the warning), the 'hi' token in the then clause as a method call, and
the 'hi' token on the final puts as a local variable.

These three issues
1) 0-argument method calls and local variables
are syntactically identical
2) local variables don't have to be declared.
3) Ruby's if/unless modifiers imply execution
order that is backwards from textual order

Thanks for the detailed explanation. :)
Another corner case is

Yep, I already know about that one.
 
M

matt neuburg

7stud -- said:
Thanks for the detailed explanation. :)


Yep, I already know about that one.

Thanks to all who contributed to this explanation - it's exactly what I
wanted to know. m.
 

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
474,268
Messages
2,571,094
Members
48,773
Latest member
Kaybee

Latest Threads

Top