My understanding is that the if
statements at the end of the line are evaluated before the code at the front of the line:
\'never shown\' if (fa
My guess is that the order of parsing is different from the (logical) order of execution. In particular, given
array << error if (error = some_function)
Then logically, execution should go something like
some_function
error
not defined, define error
some_function
to error
if
if
evaluates to true
, append value of error
to array
However, parsing wise (assuming typical LR parser), it goes
array
(identifier). Is this defined? Yes. Is it a variable? Yes.<<
(operator). Does array
respond to <<
? Yes (otherwise, output "undefined method" error).error
(identifier). Is this defined? No. Output "undefined local variable or method".It only happens when you try to assign a literal value, if you call a function it works.
def foo(a)
a
end
p 'not shown' if(value = foo(false))
p 'shown' if(value = foo(true))
# This outputs a Warning in IRB
p 'shown' if(value = false)
(irb):2: warning: found = in conditional, should be ==
If you turn on debugging (-d) you will see a warning about an used variable value
warning: assigned but unused variable - value
This "works" because the statement does evaluate to true
as far as if is concerned, allowing the code preceeding it to run.
What is happening here is that if() when used as a modifier has it's own binding scope, or context. So the assignment is never seen outside of the if, and therefore makes no sense to perform. This is different than if the control structure because the block that the if statement takes is also within the same scope as the assignment, whereas the line that preceeded the if modifier is not within the scope of the if.
In other words, these are not equivelant.
if a = some(value)
puts a
end
puts a if(a = some(value))
The former having puts a
within the scope of the if, the latter having puts a
outside the scope, and therefore having different bindings(what ruby calls context).
Ruby Order of Operations
In Ruby, local variables are defined by the parser when it first encounters an assignment, and are then in scope from that point on. Since Ruby is parsed like English, left-to-right, top-to-bottom, the local variable doesn't exist yet when you are using it, because the usage is further left from the assignment.
Here's a little demonstration:
foo # NameError: undefined local variable or method `foo' for main:Object
if false
foo = 42
end
foo # => nil
As you can see, the local variable does exist on line 7 even though the assignment on line 4 was never executed. It was, however, parsed and that's why the local variable foo
exists. But because the assignment was never executed, the variable is uninitialized and thus evaluates to nil
and not 42
.
Now let's get to simplest version of your case:
bar if bar = true
# warning: found = in conditional, should be ==
# NameError: undefined local variable or method `bar' for main:Object
bar # => true
The variable is created when the assignment is parsed, which is here:
bar if bar = true
^^^^^^^^^^
But it is used here:
bar if bar = true
^^^
Which is before the assignment. The fact that the assignment is executed before the usage is irrelevant because the parsing is relevant here, and the assignment gets parsed after the usage which means that at the point of usage the parser still thinks it's a method call with no argument list and an implicit receiver (i.e. equivalent to self.bar()
) and not a local variable.