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 ArgumentErrors. 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.