10
votes

I noticed what I find to be a very surprising behavior with the ** (double-splat) operator in Ruby 2.1.1.

When key-value pairs are used before a **hash, the hash remains unmodified; however, when key-value pairs are only used after the **hash, the hash is permanently modified.

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 }

For comparison, consider the behavior of the single-* operator on arrays:

a = [2]

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

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

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

The array remains unchanged throughout.


Do we suppose the sometimes-destructive behavior of ** is intentional, or does it look more like a bug?

In either case, where is the documentation describing how the ** operator is meant to work?


I also asked this question in the Ruby Forum.

UPDATE

The bug is fixed in Ruby 2.1.3+.

2
The use in parameter lists is in the core documentation ruby-doc.org/core-2.1.1/doc/syntax/methods_rdoc.html . Hash and array literal interpolation don't seem to appear anywhere there, though single spat at least has a spec: github.com/rubyspec/rubyspec/blob/master/language/splat_spec.rb. There's nothing similar for double splat. Ruby semantics do seem to be folkloric. I'm sure this is a bug insofar as an undocumented language feature can be buggy! - Gene
i did not even know that you are allowed to use that in a none method signature... - phoet
It looks like the composed hash is the same object as the first element in it if it is a hash (they have the same object id). That is why they are modified. When you have two hashes h and i and do {**h, **i, d: 5}, only h is modified, not i. - sawa
One more thing - If you post directly on Rubyforum, it wouldn't be available in the mailing list, whereas reverse is ok. So better post it in the mailing list. What I said is the current Gateway problem. - Arup Rakshit
@sawa It's an interesting insight that the expression's result is the same object as h, but there's also more to it. Consider h = { a: 1 }; { **h, a: 99, **h }. Since the final result is { a: 99 }, we can see that even by the time we reach the final **h, h[:a] has already been overwritten. - user513951

2 Answers

7
votes

The answers to the question seem to be:

  1. It's probably a bug, rather than intentional.

  2. The behavior of the ** operator is documented very briefly in the core library rdoc.

Thanks to the suggestions of several commenters, I've posted the bug to the Ruby trunk issue tracker.


UPDATE:

The bug was fixed in changeset r45724. The comment there was "keyword splat should be non-destructive," which makes this an authoritative answer.

1
votes

I noticed the diff between 2.1.5 and 2.3.1

Example is an irb method and a way of calling it

$ irb
>> def foo(opts) opts end
=> :foo
>> foo a: 'a', ** {a: 'b'}

In 2.1.5 the following results in retaining value

=> {:a=>"a"}

In 2.3.1 the value is 'b'

(irb):2: warning: duplicated key at line 2 ignored: :a
=> {:a=>"b"}

I am not sure which it should be?

In 2.3.1 the hash provided as double splat overrides the same key of the first item in a list.