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


80

大多数人都知道_IRB作为最后一次返回值的持有者在IRB中的特殊含义,但这不是我要问的。

相反,我问的是什么_时候在纯旧的Ruby代码中用作变量名。在这里,它似乎具有特殊的行为,类似于“不在乎变量”(la 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添加了s)而不是在IRB中运行,以避免与它的其他功能冲突。

不过,这全都是我自己实验的结果,因为我在任何地方都找不到关于此行为的任何文档(诚然,这不是最简单的搜索内容)。最终,我很好奇所有这些在内部如何运作,因此我可以更好地准确了解其特殊之处_。因此,我要求参考文档,最好是提供Ruby源代码(也许还有RubySpec)的参考资料,以揭示_Ruby的行为。

注意:大部分是由与@Niklas B的讨论引起的。

Answers:


53

源代码中有一些特殊处理可以消除“重复的参数名称”错误。错误消息仅出现在shadowing_lvar_gen内部parse.y1.9.3版本如下所示

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

并且idUScore在定义id.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中,idUScore == v[i]测试inwarn_unused_var被替换为对的调用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使事情变得松散了。


1
我想知道lambda { |_, _| _ }.call(4, 2)1.8和1.9之间的行为差异仅仅是无意的副作用吗?就像在“正常”情况下,变量名不能重复一样,它们的分配顺序无关紧要。
安德鲁·马歇尔

3
@AndrewMarshall:是的,我认为“ 4 vs 2”问题只是1.8和1.9如何处理堆栈的产物。唯一会引起注意的|_,_,...|原因是重复错误已被抑制。
亩太短

2
@AndrewMarshall:我想知道是否每个人都在背后阅读彼此的ChangeLogs。
亩太短了

2
好发现。我以为那是简单的事情,只是抑制了双参数名称错误。Ruby确实是一个大混乱:D
Niklas B. 2012年

2
@mu:当然,我还没有看到一种真正干净的解释语言实现(Lua接近)。
Niklas B.

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似乎是一种机制,通过该机制,块的形式参数会替换另一个作用域中存在的同名变量。该函数似乎引发“重复的参数名称”SyntaxError和“阴影外部局部变量”警告。

在找到grep的源代码之后shadowing_lvar_gen,我在Ruby 1.9.3的变更日志中找到了以下内容:

Tue Dec 11 11:21:21 2007 Yukihiro Matsumoto

  • parse.y(shadowing_lvar_gen):“ _”没有重复错误。

这可能是这条线的起源:

if (idUScore == name) return name;

据此,我推断出在诸如的情况下proc { |_, _| :x }.call :a, :b,一个_变量只是遮蔽了另一个变量。


这是有问题的提交。它基本上介绍了这两行:

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

从一个idUScore甚至根本不存在的时代开始。


6
这并不能解释所有为什么lambda { |_, _| 42 }而工作lambda { |x, x| 42 }却没有。
安德鲁·马歇尔

@AndrewMarshall,看来您是对的。|_, _|确实有效,但|__, __|无效。_确实有一些特殊的含义,我将看看是否可以从Ruby来源中挖掘任何信息。
Matheus Moreira'3

1
顺便说一句,您的更新虽然具有参考价值,但并不适用,因为它仅适用于IRb,我在我没有问过的问题中特别指出了这一点。
安德鲁·马歇尔

1
+1用于挖掘ChangeLog条目。每个人都应该是C黑客:)
mu太短

1
+1为IRB源代码。IRB.CurrentContext.last_value非常有趣,即使它与所提出的问题无关。我最终在Google搜索中找到有关IRB下划线的信息。
乔什
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.