Double-splat operator destructively modifies hash – is this a Ruby bug?
The answers to the question seem to be:
It's probably a bug, rather than intentional.
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.
What is the point of using Ruby's double-splat (`**`) in method calls?
The example using a single argument is the degenerate case.
Looking at a nontrivial case, you can quickly see the advantage of having the new **
operator:
def foo (args)
return args
end
h1 = { b: 2 }
h2 = { c: 3 }
foo(a: 1, **h1) # => {:a=>1, :b=>2}
foo(a: 1, **h1, **h2) # => {:a=>1, :b=>2, :c=>3}
foo(a: 1, h1) # Syntax Error: syntax error, unexpected ')', expecting =>
foo(h1, h2) # ArgumentError: wrong number of arguments (2 for 1)
Using the **
operator allows us to merge existing hashes together in the command line, along with literal key-value arguments. (The same is true of using *
with argument arrays, of course.)
Sadly, you have to be careful with this behavior, depending on what version of Ruby you're using. In Ruby 2.1.1, at least, there was a bug where the splatted hash would be destructively modified, though it's since been patched.
Make Ruby object respond to double splat operator **
You can implement to_hash
: (or define it as an alias for to_h
)
class MyClass
def to_hash
{ a: 1, b: 2 }
end
end
def foo(**kwargs)
p kwargs: kwargs
end
foo(MyClass.new)
#=> {:kwargs=>{:a=>1, :b=>2}}
Unexpected result with splat operator
Because the []=
method applied to hash
takes only one argument in addition to the key (which is put inside the []
part), and a splatted/expanded array, which is in general a sequence of values (which coincidentally happens to be a single element in this particular case) cannot be directly accepted as the argument as is splatted. So it is accepted by the argument of []=
as an array after all.
In other words, an argument (of the []=
method) must be an object, but splatted elements (such as :foo, :bar, :baz
) are not an object. The only way to interpret them as an object is to put them back into an array (such as [:foo, :bar, :baz]
).
Using the splat operator, you can do it like this:
hash.each_pair{|key, value| hash.[]= key, *value}
What does a double * (splat) operator do
Ruby 2.0 introduced keyword arguments, and **
acts like *
, but for keyword arguments. It returns a Hash with key / value pairs.
For this code:
def foo(a, *b, **c)
[a, b, c]
end
Here's a demo:
> foo 10
=> [10, [], {}]
> foo 10, 20, 30
=> [10, [20, 30], {}]
> foo 10, 20, 30, d: 40, e: 50
=> [10, [20, 30], {:d=>40, :e=>50}]
> foo 10, d: 40, e: 50
=> [10, [], {:d=>40, :e=>50}]
Expanding empty hash in variable with double splat in Ruby
Yes, it very looks like a bug
def foo(*args)
args
end
foo(**{})
# => []
h = {}
foo(**h)
# => [{}]
It passes empty hash as first argument in case of double splat of variable.
My version is ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]
Using splat operator with when
But the splat operator is about assignment, not comparison.
In this case, *
converts an array into an argument list:
when *[2, 3, 4]
is equivalent to:
when 2, 3, 4
Just like in a method call:
foo(*[2, 3, 4])
is equivalent to:
foo(2, 3, 4)
Related Topics
How to Simulate Java-Like Annotations in Ruby
Using Ruby Convert Numbers to Words
Why Does Code Need to Be Reloaded in Rails 3
How to Run a Single Rspec Test
How to Check for File Existence
How to Get Readline Support in Irb Using Rvm on Ubuntu 11.10
Monkey Patching Devise (Or Any Rails Gem)
Csv.Read Illegal Quoting in Line X
How to Uninstall Ruby on Rails on MAC Os X
Set Utf-8 as Default for Ruby 1.9.3
How to Capture Values in Command Line and Add to Recipe
Differencebetween <%= ... %> and <% ... %> in Ruby on Rails
How to Sort an Array of Hashes by a Value in the Hash
When to Use Memoization in Ruby on Rails
Foreman Only Shows Line with "Started with Pid #" and Nothing Else