Undefined local variable based on syntax in Ruby

In the following Ruby code

#! /usr/bin/env ruby x = true y = x and z = y puts "z: #{z}" 

It will output z: true , as expected.

But in the following, which I expect to have the same behavior:

 #! /usr/bin/env ruby x = true z = y if y = x puts "z: #{z}" 

The result is

undefined local variable or method 'y' for main: Object (NameError)

Why?

I realized that I was doing the assignment, and implicitly checking the assignment value to determine if z = y should be run. I also realized that if I add the declaration y, y = nil , right after the line x = 5 , it will go through and start as expected.

But is it not right to expect that the language must first evaluate the if part, and then its contents, and the second code fragment will behave the same as the first code fragment?

+7
ruby
source share
1 answer

TL; DR

This is truly interpreter specific. The problem manifests itself in Ruby 2.1.2 MRI and JRuby 1.7.13, but it works as expected in Rubinius . For example, with Rubinius 2.2.10:

 x = true z = y if y = x #=> true 

In the MRI, a small study with Ripper shows that Ruby treats the post-condition differently, even if the assignment ASTs are similar. It actually uses different tokens for post-conditions when constructing the AST, and this, apparently, affects the order of evaluation of assignment expressions. Whether this will be so or not, or it can be fixed, is a question for the Ruby Core Team .

Why does this work with logical and

 x = true y = x and z = y 

This is done because there are indeed two assignments in the sequence, because true assigned to x and therefore evaluates to true. Since the first expression is true, the next expression is logical, and is also evaluated and also evaluated as true.

 y = x #=> true z = y #=> true 

In other words, x is set to true , and then z is also set to true . In no case is the right side of any assignment undefined.

Why does this happen with the post-condition

 x = true z = y if y = x 

In this case, the precondition is satisfied first. You can see this by looking at AST :

 require 'pp' require 'ripper' x = true pp Ripper.sexp 'z = y if y = x' [:program, [[:if_mod, [:assign, [:var_field, [:@ident, "y", [1, 9]]], [:vcall, [:@ident, "x", [1, 13]]]], [:assign, [:var_field, [:@ident, "z", [1, 0]]], [:vcall, [:@ident, "y", [1, 4]]]]]]] 

Unlike your first example, where y was assigned true in the first expression and therefore allowed to true in the second expression before being assigned to z, in this case y is calculated as yet undefined. This raises a NameError .

Of course, it could legitimately be argued that both expressions contain assignments, and that y will not be truly undefined if Ruby parsing evaluates y = x first, as happens with the normal if statement (see AST below). This is probably just a fad of postoperative if statements and the way Ruby processes the token: if_mod.

Succeed With: if Instead: if_mod Tokens

If you cancel the logic and use the normal if statement, it works fine:

 x = true if y = x z = y end #=> true 

A look at Ripper gives the following AST:

 require 'pp' require 'ripper' x = true pp Ripper.sexp 'if y = x; z = y; end' [:program, [[:if, [:assign, [:var_field, [:@ident, "y", [1, 3]]], [:vcall, [:@ident, "x", [1, 7]]]], [[:assign, [:var_field, [:@ident, "z", [1, 10]]], [:var_ref, [:@ident, "y", [1, 14]]]]], nil]]] 

Note that the only real difference is that the example that throws NameError uses: if_mod, and the version that succeeds uses: if. The post-condition seems to be causing the error, quirk, or error you see.

What to do with her

There may be a good technical reason for this parsing behavior, or not. I can not judge. However, if this seems like a mistake and you are motivated to do something, the best thing would be to check the Ruby Issue Tracker to see if it has already been reported. If not, maybe the time has come when someone officially raised it.

+4
source share

All Articles