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}]
What does the double-splat do in a method call?
One might need to destructure the input parameters. In such a case simple hash won’t work:
params = {foo: 42, bar: :baz}
def t1(foo:, **params); puts params.inspect; end
#⇒ :t1
def t2(foo:, params); puts params.inspect; end
#⇒ SyntaxError: unexpected tIDENTIFIER
def t2(params, foo:); puts params.inspect; end
#⇒ :t2
Now let’s test it:
t1 params
#⇒ {:bar=>:baz}
t2 params
#⇒ ArgumentError: missing keyword: foo
t2 **params
#⇒ ArgumentError: missing keyword: foo
That said, double splat allows transparent arguments destructuring.
If one is curious why it might be useful, foo
in the example above is made a mandatory parameter in a call to the method within this syntax.
Unsplatting parameters in call to function is allowed as a sort of sanity type check to assure that all the keys are symbols:
h1 = {foo: 42}
h2 = {'foo' => 42}
def m(p); puts p.inspect; end
m **h1
#⇒ {:foo=>42}
m **h2
#⇒ TypeError: wrong argument type String (expected Symbol)
What does double splat (**) argument mean in this code example and why use it?
what does
**
mean in the block above?
It's a kwsplat, but it's not assigned a name. So this method will accept arbitrary set of keyword arguments and ignore all but :fragment
.
why use this syntax?
To ignore arguments you're not interested in.
A little demo
class Person
attr_reader :name, :age
def initialize(name:, age:)
@name = name
@age = age
end
def description
"name: #{name}, age: #{age}"
end
end
class Rapper < Person
def initialize(name:, **)
name = "Lil #{name}" # amend one argument
super # send name and the rest (however many there are) to super
end
end
Person.new(name: 'John', age: 25).description # => "name: John, age: 25"
Rapper.new(name: 'John', age: 25).description # => "name: Lil John, age: 25"
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.
Ruby Splat operator in method definition takes more memory
Let's examine the two arrays more closely:
require 'objspace'
def with_splat(*methods)
ObjectSpace.dump(methods, output: open('with_splat.json', 'w'))
end
def without_splat(methods)
ObjectSpace.dump(methods, output: open('without_splat.json', 'w'))
end
with_splat(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o)
without_splat([:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o])
ObjectSpace.dump_all(output: open('all_objects.json', 'w'))
The script generates 3 files:
with_splat.json
contains data about the splatted arraywithout_splat.json
contain data about the non splatted arrayall_objects.json
contains data about all objects (that's a lot!)
with_splat.json
: (formatted)
{
"address": "0x7feb941289a0",
"type": "ARRAY",
"class": "0x7feb940972c0",
"length": 15,
"memsize": 160,
"flags": {
"wb_protected": true
}
}
without_splat.json
: (formatted)
{
"address": "0x7feb941287e8",
"type": "ARRAY",
"class": "0x7feb940972c0",
"length": 15,
"shared": true,
"references": [
"0x7feb941328d8"
],
"memsize": 40,
"flags": {
"wb_protected": true
}
}
As you can see, the latter array does consume less memory (40 vs 160), but it also has "shared": true
set and it references another object at memory address 0x7feb941328d8
.
Let's find that object in all_objects.json
via jq:
$ jq 'select(.address == "0x7feb941328d8")' all_objects.json
{
"address": "0x7feb941328d8",
"type": "ARRAY",
"frozen": true,
"length": 15,
"memsize": 160,
"flags": {
"wb_protected": true
}
}
And that's the actual array with the very same memsize as the first array above.
Note that this array has "frozen": true
set. I assume that Ruby creates these frozen arrays when it encounters an array literal. It can then create cheap(er) shared arrays upon evaluation.
What does a splat operator do when it has no variable name?
Typically a splat like this is used to specify arguments that are not used by the method but that are used by the corresponding method in a superclass. Here's an example:
class Child < Parent
def do_something(*)
# Do something
super
end
end
This says, call this method in the super class, passing it all the parameters that were given to the original method.
source: Programming ruby 1.9 (Dave Thomas)
Double splat and default value in method profile
If the input to the method MUST be options hash, then, use double splat operator **
.
Using options = {}
only declares the default value to be empty hash, however, it does not necessarily guarantee that caller will pass hash - she may pass non-hash values and nil.
If the function was implemented using double splat (**
) - as evident in examples you have provided - then non-hash and nil values will not be accepted and will be reported as error.
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)
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}}
Related Topics
Is There a "Do ... While" Loop in Ruby
Why Does Installing Nokogiri on MAC Os Fail With Libiconv Is Missing
Rhc Setup Gives Error 'No Such File Dl/Import'
Rails Sessions Current Practices
Rails 4: Before_Filter Vs. Before_Action
Heroku Upload-Precompiling Assets Failed
What Tools Do You Recommend to Profile Rails Apps
Error When Running Rails App - Execjs::Runtimeerror
Restarting a Loop from the Top
Why Does Ruby 1.9.2 Remove "." from Load_Path, and What's the Alternative
Rvm Installation Not Working: "Rvm Is Not a Function"
Git, Heroku: Pre-Receive Hook Declined