双星号运算符会破坏性地修改哈希表 - 这是 Ruby 的一个错误吗?

10

我发现在 Ruby 2.1.1 中,**(双星号)操作符的行为非常令人惊讶。

当键值对在 **hash 之前使用时,哈希表保持不变;然而,当键值对仅在 **hash 之后使用时,哈希表将永久性地被修改。

h = { b: 2 }

{ a: 1, **h }        # => { a: 1, b: 2 }
h                    # => { b: 2 }

{ a: 1, **h, c: 3 }  # => { a: 1, b: 2, c: 3 }
h                    # => { b: 2 }

{ **h, c: 3 }        # => { b: 2, c: 3 }
h                    # => { b: 2, c: 3 }

作为比较,考虑一下单个*运算符在数组上的行为:

a = [2]

[1, *a]     # => [1, 2]
a           # => [2]

[1, *a, 3]  # => [1, 2, 3]
a           # => [2]

[*a, 3]     # => [2, 3]
a           # => [2]

整个过程中,该数组保持不变。


我们认为**有时具有破坏性的行为是故意的,还是看起来更像一个错误?

无论哪种情况,哪里有描述**运算符应如何工作的文档?


我也在Ruby论坛上问过这个问题。

更新

该漏洞已在Ruby 2.1.3+中修复。


2
使用参数列表的用法在核心文档中有详细说明,可以在http://www.ruby-doc.org/core-2.1.1/doc/syntax/methods_rdoc.html找到。然而,在那里似乎找不到哈希和数组文字插值的相关内容,尽管至少单个星号有规范可循:https://github.com/rubyspec/rubyspec/blob/master/language/splat_spec.rb。对于双星号则没有类似的规范。Ruby的语义似乎是传说中的。我相信这在某种程度上是一个错误,因为未记录的语言特性可能会出现问题! - Gene
2
如果组成的哈希表是哈希对象(它们具有相同的对象ID),则看起来组成的哈希表与其中的第一个元素是相同的对象。这就是为什么它们被修改的原因。当你有两个哈希表 hi 并执行 {**h, **i, d: 5} 时,只有 h 被修改,而不是 i - sawa
@ArupRakshit 我认为这并不重要。Ruby开发社区的响应速度较慢。 - sawa
@sawa 我曾经看到过 Are we dying?。所以问他。 :) - Arup Rakshit
3
这是一个有趣的见解,即表达式的结果与h是同一个对象,但这还不是全部。考虑 h = { a: 1 }; { **h, a: 99, **h }。由于最终结果是 { a: 99 },因此我们可以看到,即使在达到最终的 **h时,h[:a]已经被覆盖了。 - user513951
显示剩余4条评论
2个回答

7
这个问题的答案可能是:
  1. 这很可能是一个错误而不是故意的。

  2. ** 操作符的行为在核心库rdoc中有非常简短的记录。

感谢几位评论者的建议,我已将该bug发布到Ruby trunk问题跟踪器上。

更新:

该bug已在更改集r45724中得到修复。那里的注释是“关键字扩展应该是非破坏性的”,这使得它成为权威答案。


这个漏洞似乎已经被修复了。那可真快。 - sawa
@sawa 它很快,但必须说它看起来像是一个hack而不是一个修复。只是用Hash的浅拷贝覆盖前一次调用的指针,省略了完整的条件。感觉有点尴尬。 - David Unric

1
我注意到2.1.5和2.3.1之间的差异。
示例是一个irb方法和调用它的方式。
$ irb
>> def foo(opts) opts end
=> :foo
>> foo a: 'a', ** {a: 'b'}

在2.1.5版本中,以下内容会保留值。
=> {:a=>"a"}

在2.3.1中,值为'b'。
(irb):2: warning: duplicated key at line 2 ignored: :a
=> {:a=>"b"}

我不确定应该选择哪个?
在2.3.1版本中,以双星号提供的哈希值将覆盖列表中第一个项的相同键。

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