Drat! - Ruby has a Double Splat
You’re probably familiar with Ruby’s splat operator (here’s help if you’re not), but since Ruby 2.0 debuted keyword arguments, its method definitions grew a new limb: double splats. Double splats mean two things and they’re inverses of each other. If you double splat a hash, you get a comma separated list of symbol keys and values. If you double splat a list, you get a hash - exactly like single splats does with arrays. These lists are just arguments to methods, either when you define or pass them:
class Thing def initialize(a:, b:, c:) # List end end Thing.new(a: 1, b: 2, c: 3) # List
While this back-and-forth sounds like a story told quickly, a lot of power is unlocked once your code twinkles with **’s. People have had this power for a while now, and they’ve found some uses keep popping up on their horizons. Let’s star gaze the brightest occurrences together. Meet our first contender:
Obligatorily Optional
The original use for double splats:
def hello(**options) p options end hello # options: {} hello name: 'Kasper' # options: { :name => 'Kasper' }
Sure, you’re thinking now: ”This isn’t Silicon Valley, there are rules!”. I see what you’re getting at. You’ve been able to do the same thing with Ruby’s optional arguments for years:
def hello(options = {}) end
But can it do this?
Move over, Hash
Moves the hash like argument over to the double splat variable:
def hello(name = nil, **options) p name p options end hello 'Kasper' # name: 'Kasper' # options: {} hello upcase: true # name: nil # options: { :upcase => true }
Though the arguments before the hash have to be optional.
def hello(name, **options) p name p options end hello upcase: true # name: { :upcase => true } # options: {}
Off Key Splat
Mixing double splats with hashes that have fewer or more keys could get you out of your element real soon if you’re not careful.
Say, you have fewer than the required keys:
def hello(name:) p name end hello # raises "ArgumentError: missing keyword: name" as expected
But what happens when you show good faith and give more than needed:
hello name: 'Kasper', play_style: :laces_out # raises "ArgumentError: unknown keyword: play_style"
What a scam! Luckily, there are ways to deal with shakedowns like these. Either scope the keys passed to what you expect:
options = { name: 'Kasper', play_style: :laces_out } hello options.slice(:name) # Borrows `slice` from Active Support
Or capture extra keys and values even though you might not need them:
def hello(name:, **options) p name p options end hello name: 'Kasper', play_style: :laces_out # name: 'Kasper' # options: { :play_style => :laces_out }
Surely these can’t be the only options? Fret not! You can take the far nicer bet too: that people won’t send whatever to your methods.
Exploit a Splat, Expand a Hash
Double splatting a hash is really removing the braces around it - and embracing the world:
options = { a: 'b' } { c: 'd', **options } # => { :c => "d", :a => "b" }
I don’t know when this is useful. But the next thing is definitely handy:
Yield to Splat
Maybe you’ve tried this before. A method yields a hash to you and then you have to dig around in it yourself:
def hello yield name: 'Kasper' end hello { |options| p options[:name] } # Outputs "Kasper"
I was pretty surprised to find you can name the yielded arguments:
hello { |name:| p name } # Outputs "Kasper"
All the standard method rules apply, so passing more or less keyword arguments raises ArgumentError
s.
Instead of getting into an argument over that, let’s get into even more uncharted waters with this highly-unlikely-to-happen scenario. Say, a method yields a hash loaded with options, but you only care about a few? Use an anonymous splat:
def hello yield name: 'Kasper', play_style: :laces_out end hello { |name:, **| p name } # Outputs "Kasper"
I’m quite amazed this even works, but I’ll take it.
How did you find the spoils of sprinkling ** in our code? While it definitely has bling to it, many of these tricks feel more contrived than their single splat counterparts. Contrived or not, hopefully you learned something today.
At the very least you know more about keyword arguments. They push the readability contained in method definitions to every caller. This way finding the right terminology has a greater ripple effect through your code. That’s a great way to increase your productivity and happiness. Then keep thinking those happy thoughts and it just might fly.