How to define a method in ruby using splat and an optional hash at the same time?
You can't do that. You have to think about how Ruby would be able to determine what belongs to *ary
and what belongs to the optional hash. Since Ruby can't read your mind, the above argument combination (splat + optional) is impossible for it to solve logically.
You either have to rearrange your arguments:
def test(id, h, *a)
In which case h
will not be optional. Or then program it manually:
def test(id, *a)
h = a.last.is_a?(Hash) ? a.pop : nil
# ^^ Or whatever rule you see as appropriate to determine if h
# should be assigned a value or not.
Optional argument after splat argument
Thanks @maerics and @JorgWMittag -
When you have a splat, it reserves all arguments, which is why it was not liking my second "options" argument. I fixed this issue by changing my arguments around to -
def calculate(*arguments)
options = arguments[-1].is_a?(Hash) ? arguments.pop : {}
options[:add] = true if options.empty?
return add(*arguments) if options[:add]
return subtract(*arguments) if options[:subtract]
end
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)
Why can I have required parameters after a splat in Ruby but not optional ones?
Ruby's argument binding semantics are already pretty complex. Consider this method:
def foo(m1, m2, o1=:o1, o2=:o2, *splat, m3, m4,
ok1: :ok1, mk1:, mk2:, ok2: :ok2, **ksplat, &blk)
local_variables.map {|var| [var, eval(var.to_s)] }.to_h
end
method(:foo).arity
# => -5
method(:foo).parameters
# => [[:req, :m1], [:req, :m2], [:opt, :o1], [:opt, :o2], [:rest, :splat],
# [:req, :m3], [:req, :m4], [:keyreq, :mk1], [:keyreq, :mk2],
# [:key, :ok1], [:key, :ok2], [:keyrest, :ksplat], [:block, :blk]]
Can you tell at first glance what the result of the following invocations will be?
foo(1, 2, 3, 4)
foo(1, 2, 3, mk1: 4, mk2: 5)
foo(1, 2, 3, 4, mk1: 5, mk2: 6)
foo(1, 2, 3, 4, 5, mk1: 6, mk2: 7)
foo(1, 2, 3, 4, 5, 6, mk1: 7, mk2: 8)
foo(1, 2, 3, 4, 5, 6, 7, mk1: 8, mk2: 9)
foo(1, 2, 3, 4, 5, 6, 7, 8, mk1: 9, mk2: 10)
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11)
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12)
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13)
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13, k4: 14)
foo(1, 2, 3, 4, 5, 6, 7, 8,
ok1: 9, ok2: 10, mk1: 11, mk2: 12, k3: 13, k4: 14) do 15 end
Now, imagine adding optional parameters with default arguments after the splat parameter to that list. It's not impossible to find sane semantics for that, but it may lead to some non-obvious results.
Can you come up with simple, sane, backwards-compatible, and non-surprising semantics?
BTW: here's the cheatsheet for the method at the top:
foo(1, 2, 3, 4)
# ArgumentError: missing keywords: mk1, mk2
foo(1, 2, 3, mk1: 4, mk2: 5)
# ArgumentError: wrong number of arguments (3 for 4+)
foo(1, 2, 3, 4, mk1: 5, mk2: 6)
# => { m1: 1, m2: 2, o1: :o1, o2: :o2, splat: [], m3: 3, m4: 4,
# ok1: :ok1, mk1: 5, mk2: 6, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, mk1: 6, mk2: 7)
# => { m1: 1, m2: 2, o1: 3, o2: :o2, splat: [], m3: 4, m4: 5,
# ok1: :ok1, mk1: 6, mk2: 7, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, mk1: 7, mk2: 8)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [], m3: 5, m4: 6,
# ok1: :ok1, mk1: 7, mk2: 8, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, mk1: 8, mk2: 9)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5], m3: 6, m4: 7,
# ok1: :ok1, mk1: 8, mk2: 9, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8, mk1: 9, mk2: 10)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: :ok1, mk1: 9, mk2: 10, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: :ok2, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13, k4: 14)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14},
# blk: nil }
foo(1, 2, 3, 4, 5, 6, 7, 8,
ok1: 9, ok2: 10, mk1: 11, mk2: 12, k3: 13, k4: 14) do 15 end
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14},
# blk: #<Proc:0xdeadbeefc00l42@(irb):15> }
Handling a method with an optional argument followed by a list of keyword arguments
See, you have **options
as an argument which do not have any default value & first argument have default value. So understand following single argument case,
Whenever single argument is passed it is tried to assign to second argument (as first one is holding default nil) & if it fails due to type mismatch then it assign to first argument. That's how my_method(4)
works.
Now Suppose you have single argument passed as hash, which match to assign to 2nd argument then of course it get assigned to second argument & first is set default nil.
If you want to make it work, then you can do following,
> my_method({sd: 4}, {})
=> [{:sd=>4}, {}]
Or you can provide argument name while passing,
> my_method(subject: {sd: 4})
=> [{:sd=>4}, {}]
splat operator on hash for keyword arguments in ruby method definition
Yeah... that's not a thing you can do.
**foo
in an argument list is how you collect a kwargs hash, so it can't also be how you inject one.
More importantly, the main point of kwargs is that they explode the hash into local variables -- that can't work if it's expanding a hash at runtime.
The closest you could get would be something like:
def initialize(**values)
values = DEFAULTS.merge(values)
raise "..." unless (values.keys - DEFAULTS.keys).empty?
@a = values[:a]
@b = values[:b]
end
Ruby Methods and Optional parameters
The way optional arguments work in ruby is that you specify an equal sign, and if no argument is passed then what you specified is used. So, if no second argument is passed in the second example, then
{age: 27, weight: 160, city: "New York"}
is used. If you do use the hash syntax after the first argument, then that exact hash is passed.
The best you can do is
def my_info2(name, options = {})
options = {age: 27, weight: 160, city: "New York"}.merge(options)
...
Related Topics
Ruby: Too Many Open Files @ Rb_Sysopen
Differencebetween Unicorn and Unicorn_Rails
+= Operator Appears to Modify Frozen String
Is Ruby Any Good for Gui Development
Capybara Trouble Filling in Js Modal
Why Does 'Puts(Nil or 4)' Fail in Ruby
Ruby on Rails: Converting "Somewordhere" to "Some Word Here"
How to Replace the Characters in a String
Why Is the << Operation on an Array in Ruby Not Atomic
Ruby: How to Call Function Before It Is Defined
Trouble Installing Ruby 1.9.2 with Rvm MAC Os X
Ruby 'Gets' That Works Over Multiple Lines