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.