【问题标题】:Where and how is the _ (underscore) variable specified?_(下划线)变量在哪里以及如何指定?
【发布时间】:2012-03-22 12:09:29
【问题描述】:

大多数人都知道 _ 在 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 中运行的(添加了putss)——不是 IRB——以避免与其附加功能发生冲突。

不过,这都是我自己实验的结果,因为我在任何地方都找不到关于这种行为的任何文档(诚然,这不是最容易搜索的东西)。最终,我很好奇所有这些在内部是如何工作的,因此我可以更好地了解_ 的特别之处。所以我要求参考文档,最好是 Ruby 源代码(可能还有 RubySpec),它揭示了 _ 在 Ruby 中的行为方式。

注意:这大部分来自this discussion@Niklas B.

【问题讨论】:

    标签: ruby


    【解决方案1】:

    源中有一些特殊处理来抑制“重复参数名称”错误。错误信息只出现在shadowing_lvar_gen里面parse.ythe 1.9.3 version looks like this

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

    idUScoredefined in 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 中,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 稍微松了一点。

    【讨论】:

    • 我想知道 lambda { |_, _| _ }.call(4, 2) 在 1.8 和 1.9 之间的行为差​​异是否只是无意的副作用呢?在“正常”情况下,变量名不能重复,它们被分配的顺序是无关紧要的。
    • @AndrewMarshall:是的,我认为“4 vs 2”问题只是 1.8 和 1.9 如何处理堆栈的产物。唯一值得注意的是|_,_,...|,因为重复错误已被抑制。
    • @AndrewMarshall:我想知道是否每个人都在背后阅读彼此的变更日志。
    • 不错的发现。我认为它会像那样简单,只是抑制双参数名称错误。 Ruby 真的是一团糟:D
    • @mu:当然,我还没有看到解释语言的真正干净的实现(Lua 接近)。
    【解决方案2】:

    _ 是一个有效的标识符。标识符不能只包含下划线,它们也可以一个下划线。

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

    您也可以将其用作方法名称:

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

    当然,它并不完全是一个可读的名称,也不会向读者传递任何关于变量引用什么或方法做什么的信息。

    IRB,特别是将_ 设置为最后一个表达式的值:

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

    因为它是in the source code,它只是将_ 设置为最后一个值:

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

    做了一些存储库探索。这是我发现的:

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

    REGISTER_SYMID(idUScore, "_");
    

    grepidUScore 的来源给了我两个看似相关的结果:

    shadowing_lvar_gen 似乎是块的形式参数替换另一个作用域中存在的同名变量的机制。该函数似乎引发了“重复的参数名称”SyntaxError 和“隐藏外部局部变量”警告。

    grepshadowing_lvar_gen 的来源后,我找到了以下on the changelog for Ruby 1.9.3

    2007 年 12 月 11 日星期二 01:21:21 松本幸弘

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

    这很可能是this line的出处:

    if (idUScore == name) return name;
    

    据此,我推断在proc { |_, _| :x }.call :a, :b 这样的情况下,一个_ 变量只会影响另一个变量。


    这里是the commit in question。它基本上介绍了这两行:

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

    显然,从idUScore 甚至不存在的时候开始。

    【讨论】:

    • 这并不能完全解释为什么lambda { |_, _| 42 }有效而lambda { |x, x| 42 }无效。
    • @AndrewMarshall,看来你是对的。 |_, _| 确实有效,但 |__, __| 无效。 _ 好像有什么特别的意思,我看看能不能从 Ruby 源码中挖掘到任何信息。
    • 顺便说一句,您的更新虽然内容丰富,但并不相关,因为它仅适用于 IRb,我在我没有询问的问题中特别说明了这一点。
    • +1 用于挖掘 ChangeLog 条目。每个人都应该是 C 黑客 :)
    • +1 用于 IRB 源代码。 IRB.CurrentContext.last_value 非常有趣,即使它与提出的问题无关。我最终从关于 IRB 下划线的谷歌搜索到这里。
    猜你喜欢
    • 2016-08-05
    • 2019-03-28
    • 2015-02-13
    • 2013-05-23
    • 2020-06-06
    • 2020-05-02
    • 1970-01-01
    • 2013-01-29
    • 1970-01-01
    相关资源
    最近更新 更多