Splat goes Ruby

You’ve probably seen an argument list being splat before, but maybe you don’t know what it means? In Ruby, it can mean two things and they’re inverses of each other. If you splat an array, you get a list. If you splat a list, you get an array. Now you probably know what an array in Ruby is, but what’s a list? 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, :b, 213) # List

While this back-and-forth sounds like it would be a story told quickly, there’s a lot of power unlocked once you start making your Ruby code twinkle with *’s all over. People have dealt with this power for years - a new one’s sprung up too - and they’ve found a number of common uses keep popping up on their horizons. Let’s star gaze the brightest occurrences together. Meet our first contender:

Arggh Matey

The original splat which turns a list into an array:

def ahoy(*args)
  p args
end

ahoy :a, 345, hello: :world # => [:a, 345, {:hello=>:world}]

Reverse Arrgh Matey

Turn an array back into a list:

def ahoy(from, to)
  puts "#{to.capitalize}, #{from.capitalize} says ahoy!"
end

even_stephens = %w(steven stephen)
ahoy even_stephens # Array is interpreted as the first argument, and to_matey won’t be set. An ArgumentError is raised.

ahoy *even_stephens # Array is reversed to a list and all the arguments are filled out.
# => Stephen, Steven says ahoy!

The Mixer

You’re not limited to a single splat either:

def hello(name, *args, options, &block)
  p name
  p args
  p options
  p block
end

hello('denny', :a, :b, upcase: true) { 'block this!' }
# name: ”denny”
# args: [:a, :b]
# options: {:upcase=>true}
# => #<Proc:0x007fc1a10afd60>

See? You can splat before a required argument just fine. Splatting before any optional arguments though?

# Works fine:
def hello(name = nil, *args)

# Throws SyntaxError:
def hello(*args, name = nil)

# Using several splats in a definition SyntaxErrors too:
def hello(a, *args, b, *brgs)

Ruby wouldn’t know where to cut off the splats, so the SyntaxError’s are well deserved.

Yield to Splat

Splats are great to index into the parameters yielded to a block. Let’s look at subscribing to an Active Support notification, which yield a name, start, finish, id, and payload when the notification is broadcast. What if you only care about the payload? Sure, you could _ all the other arguments, but there’s a simpler more intention revealing way:

ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
  puts payload
end

ActiveSupport::Notifications.subscribe('render') do |*, payload|
  puts payload
end

# It's also easier than:
ActiveSupport::Notifications.subscribe('render') do |*args|
  payload = args.last
  puts payload
end

This saves you and your fellow programmers the trouble of reading about variables not used.

Assassinate & Assign

Be the hero your code deserves by saving the arguments you need and dispose of the rest with ease:

a, b = [:a, :b]
a # => :a
b # => :b

a, b = [:a, :b, :c] # :c is lost
a # => :a
b # => :b

a, *rest = [:a, :b, :c]
a # => :a
rest # => [:b, :c]

If you don’t care about the rest? Don’t name it:

a, *= [:a, :b, :c]
a # => :a

You can even rely on Ruby’s implicit splatting and rewrite the above as:

a ,= [:a, :b, :c]
a # => :a

Arrrg, Couldn’t Care Less

Again, if you don’t care about the arguments? Don’t name them:

class WhipperSnapper
  def initialize(snap_count, whip_size)
    @snap_count, @whip_size = snap_count, whip_size
  end
end

class SupremeSnapper < WhipperSnapper
  def initialize(*)
    super
    @agility = 100_000
  end
end

You’ll still have to pass snap_count and whip_size if you instantiate a SupremeSnapper - the free pass is only in the method definition.

How did you find the spoils of sprinkling * in our code? To me Ruby’s splatting is a near and dear part of what makes it so special. Ruby has long prided itself of making programmers happier and nowhere is that more evident than in Ruby’s expressive method definitions. There’s so much you can do with it, and - with good variable names - it’s still readable. That’s a rare feat, which most languages don’t have figured out. And no, type annotations ain’t f***ing it.