基于 Ruby 语法的未定义局部变量 [英] Undefined local variable based on syntax in Ruby

查看:46
本文介绍了基于 Ruby 语法的未定义局部变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在以下 Ruby 代码中,

In the following Ruby code,

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

它会像预期的那样输出 z: true.

It will output z: true, as expected.

但是在下面的一个中,我希望有相同的行为:

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

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

结果

main:Object (NameError) 的未定义局部变量或方法 'y'

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

这是为什么?

我明白我在做一个赋值,隐式检查赋值值以确定是否运行z = y.我也明白,如果我在 x = 5 行之后添加 y, y = nil 的声明,它将按预期通过并运行.

I understood I am doing an assignment, and implicitly check for the assignment value to determine whether to run z = y. I also understood that if I add declaration of y, y = nil, right after the x = 5 line, it will pass and run as expected.

但是,期望语言应该首先评估 if 部分,然后评估其内容,然后第二块代码的行为与第一块代码的行为相同,这不是正确的吗?>

But isn't it correct to expect that the language should evaluate the if part first and then its content, and second chunk of code to behave the same as the first chunk of code?

推荐答案

TL;DR

这实际上是特定于解释器的.该问题出现在 MRI Ruby 2.1.2 和 JRuby 1.7.13 中,但在 Rubinius 中按预期工作.例如,使用 Rubinius 2.2.10:

TL;DR

This is actually interpreter-specific. The problem shows up in MRI Ruby 2.1.2 and JRuby 1.7.13, but works as expected in Rubinius. For example, with Rubinius 2.2.10:

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

在 MRI 中,使用 开膛手 表明即使 AST 赋值是相似的,Ruby 对待后置条件的方式也不同.在构建 AST 时,它实际上为后置条件使用了不同的标记,这似乎对赋值表达式的求值顺序有影响.这是否应该是这种情况,或者是否可以修复,这是 Ruby 核心团队.

In MRI, a little exploration with Ripper shows that Ruby treats the post-condition differently even though the AST assignments are similar. It actually uses different tokens for post-conditions when building the AST, and this appears to have an effect on the evaluation order of assignment expressions. Whether or not this should be the case, or whether it can be fixed, is a question for the Ruby Core Team.

x = true
y = x and z = y

之所以成功,是因为它实际上是按顺序进行的两次赋值,因为 true 被赋值给了 x,因此计算结果为真.由于第一个表达式是真值,下一个由逻辑 and 连接的表达式也被求值,并且同样被求值为真值.

This succeeds because it's really two assignments in sequence, because true is assigned to x and therefore evaluates as truthy. Since the first expression is truthy, the next expression connected by the logical and is also evaluated and likewise evaluates as truthy.

y = x
#=> true

z = y
#=> true

换句话说,x被赋值为true,然后z也被赋值为true>.任何一个赋值的右侧都没有未定义.

In other words, x is assigned the value true, and then z is also assigned the value true. At no point is the right-hand side of either assignment undefined.

x = true
z = y if y = x

在这种情况下,实际上首先评估后置条件.您可以通过查看 AST 来了解这一点:

In this case, the post-condition is actually evaluated first. You can see this by looking at the 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]]]]]]]

与您的第一个示例不同,在第一个表达式中 y 被分配了 true,因此在第二个表达式中解析为 true分配给 z,在这种情况下 y 被评估时仍然未定义.这会引发 NameError.

Unlike your first example, where y was assigned true in the first expression, and therefore resolved to true in the second expression before being assigned to z, in this case y is evaluated while still undefined. This raises a NameError.

当然,人们可以合理地争辩说两个表达式都包含赋值,并且如果 Ruby 的解析器像它那样首先计算 y = x,那么 y 就不会真正未定义使用正常的 if 语句(参见下面的 AST).这可能只是后置条件 if 语句的一个怪癖以及 Ruby 处理 :if_mod 标记的方式.

Of course, one could legitimately argue that both expressions contain assignments, and that y wouldn't really be undefined if Ruby's parser evaluated y = x first as it does with a normal if statement (see AST below). This is probably just a quirk of post-condition if statements and the way Ruby handles the :if_mod token.

如果你颠倒逻辑并使用普通的 if 语句,它工作正常:

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

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

查看 Ripper 产生以下 AST:

Looking at Ripper yields 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]]]

请注意,唯一真正的区别是引发 NameError 的示例使用 :if_mod,而成功的版本使用 :if.您所看到的错误、怪癖或功能错误的原因似乎肯定是后置条件.

Note that the only real difference is that the example that raises NameError uses :if_mod, while the version that succeeds uses :if. It certainly seems like the post-condition is the cause of the bug, quirk, or misfeature that you're seeing.

这种解析行为可能有很好的技术原因,也可能没有.我没有资格判断.然而,如果它看起来像一个错误,并且你有动力去解决它,最好的办法是检查 Ruby 问题跟踪器 以查看它是否已被报告.如果没有,也许是时候有人正式提出来了.

There may be a good technical reason for this parsing behavior, or there may not. I'm not qualified to judge. However, if it looks like a bug to you, and you're motivated to do something about it, the best thing to do would be to check the Ruby Issue Tracker to see if it's already been reported. If not, maybe it's time someone brought it up formally.

这篇关于基于 Ruby 语法的未定义局部变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆