baash05
baash05

Reputation: 4516

Variable scope and order of parsing vs. operations: Assignment in an "if"

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 (false)

And assignment is possible in an if statement.

'shown' if (value = 'dave is king')
value #=> "dave is king"

And, when a variable that doesn't exist is assigned to, it is created. There is no need for it to exist beforehand. Is this true?

If all these assumptions are true, why does this fail?

error_array << error if (error = import_value(value))
#=> undefined local variable or method `error' for

It assigned to error before the array push right? I want to understand when things are evaluated.

This one does work:

if (error = import_value(value))
  error_array << error
end

Now I'm really confused.

Upvotes: 4

Views: 425

Answers (3)

J&#246;rg W Mittag
J&#246;rg W Mittag

Reputation: 369614

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.

Upvotes: 4

Cluster
Cluster

Reputation: 5626

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

Upvotes: 5

Alistair A. Israel
Alistair A. Israel

Reputation: 6577

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

  1. call some_function
  2. if error not defined, define error
  3. assign return value of some_function to error
  4. evaluate if
  5. if if evaluates to true, append value of error to array

However, parsing wise (assuming typical LR parser), it goes

  1. Got token array (identifier). Is this defined? Yes. Is it a variable? Yes.
  2. Got token << (operator). Does array respond to <<? Yes (otherwise, output "undefined method" error).
  3. Got token error (identifier). Is this defined? No. Output "undefined local variable or method".

Upvotes: 2

Related Questions