Ruby keyword arguments, the double splat, and **_
Ruby 2.0 introduced support for keyword arguments.
If you’re already familiar with keyword arguments, feel free to skip to the next bit!
Keyword arguments
Keyword arguments give you a convenient shorthand for using hashes as an argument to a method. So something like the following:
def foo(args)
raise ArgumentError, "missing keyword: key" unless args[:key]
if (unknowns = args.keys.select { |key| key != :key }).any?
raise ArgumentError, "unknown keywords: #{unknowns.join(", ")}"
end
key = args[:key]
key
end
foo(key: "value") # => "value"
foo({}) # ArgumentError: missing keyword: key
foo(other_key: "other_value") # ArgumentError: missing keyword: key
foo(key: "value", other_key: "other_value") # ArgumentError: unknown keywords: other_key
def bar(args = {})
value = args.fetch(:key, "default_value")
if (unknowns = args.keys.select { |key| key != :key }).any?
raise ArgumentError, "unknown keywords: #{unknowns.join(", ")}"
end
value
end
bar(key: "value") # => "value"
bar({}) # => "default_value"
bar(other_key: "other_value") # ArgumentError: unknown keywords: other_key
bar(key: "value", other_key: "other_value") # ArgumentError: unknown keywords: other_key
Can now be written like this:
def foo(key:)
key
end
foo(key: "value") # => "value"
foo({}) # ArgumentError: missing keyword: key
foo(other_key: "other_value") # ArgumentError: missing keyword: key
foo(key: "value", other_key: "other_value") # ArgumentError: unknown keyword: other_key
def bar(key: "default_value")
key
end
bar(key: "value") # => "value"
bar({}) # => "default_value"
bar(other_key: "other_value") # ArgumentError: unknown keyword: other_key
bar(key: "value", other_key: "other_value") # ArgumentError: unknown keyword: other_key
Double splat!
There is a new operator, the double splat, that is sort of the splat for keyword arguments. This is most easily explained in yet another “before and after” example.
Ruby 1.9:
def foo(args)
raise ArgumentError, "missing keyword: key" unless args[:key]
key = args[:key]
everything_else = args.delete_if { |k, _| k == :key}
[key, everything_else]
end
foo(key: "value", other_key: "other_value") # => ["value", {:other_key=>"other_value"}]
Ruby 2.0:
def foo(key:, **everything_else)
[key, everything_else]
end
foo(key: "value", other_key: "other_value") # => ["value", {:other_key=>"other_value"}]
Pretty cute!
**_, the starsnake
But what if I want to use the new shorthand syntax without raising an error on any extra unused keys? In code:
def foo(args)
raise ArgumentError, "missing keyword: key" unless args[:key]
# if (unknowns = args.keys.select { |key| key != :key }).any?
# raise ArgumentError, "unknown keywords: #{unknowns.join(", ")}"
# end
key = args[:key]
key
end
foo(key: "value") # => "value"
foo(key: "value", other_key: "other_value") # => "value"
Introducing, the starsnake **_ (sorry, couldn’t come up with a good name).
def foo(key:, **_)
key
end
foo(key: "value") # => "value"
foo(key: "value", other_key: "other_value") # => "value"
If you don’t know about Ruby’s magic underscore (a syntax borrowed from Perl) you can read about it here. Basically, _
is Ruby’s variable name for storing values you don’t need.
There may be some debate as to whether having a triple symbol expression or allowing arbitrary keys into your method is actually a good idea, but that’s outside the scope of this post. ;)