下划线变量在哪里以及如何指定?

92
大多数人都知道在IRB中_的特殊含义是作为最后一个返回值的占位符,但这不是我在这里询问的内容。
相反,我在询问在普通的Ruby代码中将_用作变量名时的情况。在这里,它似乎具有特殊行为,类似于“不关心变量”(如Prolog)。以下是一些有用的示例,说明了它的独特行为:
lambda { |x, x| 42 }            # SyntaxError: duplicated argument name
lambda { |_, _| 42 }.call(4, 2) # => 42
lambda { |_, _| 42 }.call(_, _) # NameError: undefined local variable or method `_'
lambda { |_| _ + 1 }.call(42)   # => 43
lambda { |_, _| _ }.call(4, 2)  # 1.8.7: => 2
                                # 1.9.3: => 4
_ = 42
_ * 100         # => 4200
_, _ = 4, 2; _  # => 2

这些都是直接在Ruby中运行的(加入了puts),而不是在IRB中运行,以避免与其额外的功能发生冲突。

这些都是由我的实验结果产生的,因为我无法在任何地方找到关于这种行为的文档(必须承认,这不是最容易搜索的东西)。最终,我很好奇所有这一切是如何在内部工作的,以便更好地了解_有什么特别之处。因此,我请求提供有关文档的参考资料,最好提供Ruby源代码(也许包括RubySpec),以揭示_在Ruby中的行为方式。

注:其中大部分是由@Niklas B.this discussion引起的。

2个回答

57

源代码中有一些特殊处理来抑制“重复参数名称”错误。该错误消息只在parse.yshadowing_lvar_gen内部出现,1.9.3版本看起来是这样的

static ID
shadowing_lvar_gen(struct parser_params *parser, ID name)
{
    if (idUScore == name) return name;
    /* ... */

而且idUScoreid.c中被定义,就像这样:

REGISTER_SYMID(idUScore, "_");

你会在warn_unused_var中看到类似的特殊处理:

static void
warn_unused_var(struct parser_params *parser, struct local_vars *local)
{
    /* ... */
    for (i = 0; i < cnt; ++i) {
        if (!v[i] || (u[i] & LVAR_USED)) continue;
        if (idUScore == v[i]) continue;
        rb_compile_warn(ruby_sourcefile, (int)u[i], "assigned but unused variable - %s", rb_id2name(v[i]));
    }
}
你会注意到警告在 for 循环的第二行被抑制了。
我在 1.9.3 源代码中找到的唯一与 _ 相关的特殊处理在上面:重复命名错误和未使用变量警告都被抑制了。除了这两个之外,_ 就像任何其他普通的变量一样。我不知道有关于 _ 的(轻微)特殊性的任何文档。
在 Ruby 2.0 中,warn_unused_var 中的 idUScore == v[i] 测试被替换为对 is_private_local_id 的调用:
if (is_private_local_id(v[i])) continue;
rb_warn4S(ruby_sourcefile, (int)u[i], "assigned but unused variable - %s", rb_id2name(v[i]));

并且 is_private_local_id 会抑制以 _ 开头的变量的警告:

if (name == idUScore) return 1;
/* ... */
return RSTRING_PTR(s)[0] == '_';

2.0相对于仅仅_本身而言,放松了一些限制。


3
是的,我认为“4对2”问题只是1.8和1.9处理堆栈的方式所产生的副作用。唯一可能会注意到它的时候是在 |_,_,...| 中,因为重复错误已被抑制。 - mu is too short
1
@muistooshort 尽管在 RubySpec 中似乎不存在,但在 JRuby 和 Rubinius 中它似乎是相同的。 - Andrew Marshall
2
@AndrewMarshall:我想知道大家是否在我们背后阅读彼此的ChangeLogs。 - mu is too short
2
不错的发现。我以为会是像那样简单的事情,只是压制了双参数名称错误。Ruby真的是一团糟:D - Niklas B.
2
@mu:当然,我还没有看到一个真正干净的解释型语言实现(Lua接近)。 - Niklas B.
显示剩余4条评论

24

_是一个有效的标识符。标识符不仅可以包含下划线,还可以下划线。

_ = o = Object.new
_.object_id == o.object_id
# => true

你也可以将它作为方法名使用:

def o._; :_ end
o._
# => :_

当然,这并不是一个易读的变量名,并且它也不能向读者传递有关该变量代表什么或该方法的作用的任何信息。

IRB 特别地,将 _ 设置为上一个表达式的值:

$ irb
> 'asd'
# => "asd"
> _
# => "asd"

正如源代码中所示,它只是将_设置为最后一个值:

@workspace.evaluate self, "_ = IRB.CurrentContext.last_value"

我进行了一些存储库探索。以下是我发现的内容:

在文件 id.c 的最后几行中,有一次调用:

REGISTER_SYMID(idUScore, "_");

在源代码中使用 grep 查找 idUScore,得到了两个似乎相关的结果:

shadowing_lvar_gen 似乎是块的形式参数替换另一个同名变量的机制,它是引发 "duplicated argument name" SyntaxError 和 "shadowing outer local variable" 警告的函数。

在源代码中使用 grep 查找 shadowing_lvar_gen 后,在 Ruby 1.9.3 的 更新日志 中找到了以下信息:

Tue Dec 11 01:21:21 2007 Yukihiro Matsumoto

  • parse.y (shadowing_lvar_gen): no duplicate error for "_".

这很可能就是 这行代码 的起源:

if (idUScore == name) return name;

根据这个,我推断在这种情况下 proc { |_, _| :x }.call :a, :b ,一个 _ 变量只是遮盖了另一个。


这就是相关提交。它基本上引入了这两行:

if (!uscore) uscore = rb_intern("_");
if (uscore == name) return;

显然是在 idUScore 不存在的时期。


7
“这完全没有解释为什么 lambda { |x, x| 42 } 不工作,而 lambda { |_, _| 42 } 工作。” - Andrew Marshall
1
@AndrewMarshall,看来你是对的。|_, _| 是可以工作的,但 |__, __| 不行。_ 确实似乎有一些特殊含义,我会看看能否从 Ruby 源代码中挖掘到任何信息。 - Matheus Moreira
1
顺便说一下,你的更新虽然信息丰富,但并不相关,因为它只适用于IRb,而我在我的问题中明确指出我没有询问这个。 - Andrew Marshall
1
+1 为挖掘 ChangeLog 条目点赞。每个人都应该成为 C 程序员 :) - mu is too short
1
+1 对于 IRB 源代码。即使与问题无关,了解 IRB.CurrentContext.last_value 也非常有趣。我是通过谷歌搜索 IRB 下划线而来到这里的。 - Josh
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接