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.
Hello!
On Jan 22, 2008 6:40 PM, matt neuburg [email protected] wrote:
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
irb
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.
irb
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.
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
Thomas W. [email protected] wrote:
Hello!
On Jan 22, 2008 6:40 PM, matt neuburg [email protected] wrote:
Here’s a simple variable initialization / scope question. In the
following code:
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.
matt neuburg wrote:
to create / initialize “result”.
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
matt neuburg wrote:
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@:1(irb)
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
Justin C. [email protected] wrote:
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.
On Jan 22, 2008 11:29 PM, matt neuburg [email protected] wrote:
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.
Jesús Gabriel y Galán wrote:
On Jan 22, 2008 11:29 PM, matt neuburg [email protected] wrote:
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.
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?
On 2008-01-23 02:40 +0900 (Wed), matt neuburg wrote:
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
On Jan 24, 2008 10:38 AM, 7stud – [email protected] wrote:
Is it expected behavior? m.
=> nil
That’s why line 002 outputs “hi”, because the first reference has been
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?
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.
On Jan 24, 2008, at 4:38 AM, 7stud – wrote:
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
- 0-argument method calls and local variables
are syntactically identical
- local variables don’t have to be declared.
- 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
On 23 Jan 2008, at 23:02, Curt S. wrote:
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
7stud – [email protected] wrote:
puts "#{hi}"
- 0-argument method calls and local variables
Yep, I already know about that one.
Thanks to all who contributed to this explanation - it’s exactly what I
wanted to know. m.
Gary W. wrote:
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
- 0-argument method calls and local variables
are syntactically identical
- local variables don’t have to be declared.
- 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.