DEV Community 👩‍💻👨‍💻

Cover image for The Splat Operator
Meagan Waller
Meagan Waller

Posted on • Originally published at meaganwaller.com

The Splat Operator

This blog is a part of my "rework" series where I rewrite a blog post that I wrote while I was an apprentice software developer. I have lots more knowledge and want to revisit these old posts, correct where I'm wrong, and
expand where I now have more in-depth experience. If you'd like to, you can read my initial post about Ruby's splat operator.

What is the splat operator?

Programmers call ! bangs, . dots, => hash rockets (this one may be Ruby specific), and * splats. Calling the asterisk a splat has a history in computing. In Commodore file systems, an asterisk appearing next to a
filename was called a "splat file" 1.

The splat operator is the *, think of it like saying etc when writing code. In Ruby, a splat is a unary operator that explodes an array (or an object that implements to_a) and returns an array.

This definition might seem kind of heady, so I'm going to bring it back down to earth with some concrete examples. First, we will start with some simple use cases to explore what's going on, and then we will dig into some
Ruby code that takes advantage of the splat.

Splats aren't exclusive to Ruby, but this post's scope only covers using them in Ruby.

Expected behaviors

Splats have a few different expected behaviors. We will explore how to use them to do various things and look at test cases from the Ruby language spec. The examples below are purely for illustrative purposes and are not necessarily examples of sophisticated code architecture.

Creating a method

An expected splat behavior is to create a method that accepts a variable number of arguments. Everything passed to the splatted parameter is collected inside an array with the same name as the parameter.

def example(a, *args)
  puts a
  puts args.inspect
end

example("a", "b", "c", "d")
Enter fullscreen mode Exit fullscreen mode
a
["b", "c", "d"]
Enter fullscreen mode Exit fullscreen mode

The rest of the array is now available inside of args.

The arguments you pass don't all have to be the same type either:

def example(a, *args)
  puts a
  puts args.inspect
end

example("a", 1, true, [1, 2, 3])
Enter fullscreen mode Exit fullscreen mode
a
[1, true, [1, 2, 3]]
Enter fullscreen mode Exit fullscreen mode

Splats can go anywhere

The most common place to put a splat is at the end of the parameter list; after all, it does act as the etc. But the splatted parameter can go anywhere.

def example(a, *args, c)
  puts a
  puts args.inspect
  puts c
end

example("a", 1, true, [1, 2, 3], "Hello")
Enter fullscreen mode Exit fullscreen mode
a
[1, true, [1, 2, 3]]
Hello
Enter fullscreen mode Exit fullscreen mode

While a splat can anywhere, it can't go everywhere. The limit is one splat per method argument list because how would Ruby know where one ends, and the other begins?

def example(*a, *b)
  puts a.inspect
  puts b.inspect
end

example("a", "b", "c", "d")
Enter fullscreen mode Exit fullscreen mode
SyntaxError: unexpected *
def example(*a, *b)
                ^
Enter fullscreen mode Exit fullscreen mode

Invoking a method

The above example is how to use a splat when defining a method. We can do the inverse and use a splat when invoking a method, too. We have a function that expects three arguments, name, age, favorite_color, and an array of triples (an array containing three items), but we don't want to destructure the triples and then pass them into the method. Splat is the operator to handle this, but first, let's look at how we might accomplish what we want without a splat:

def user_info(name, age, favorite_color)
  puts "#{name} is #{age} years old and their favorite color is #{favorite_color}"
end

users = [
  ["Meagan", 28, "pink"],
  ["Lauren", 25, "purple"],
  ["Bob", 32, "green"],
  ["Leonard", 7, "blue"]
]

users.each { |user| user_info(user[0], user[1], user[2]) }
Enter fullscreen mode Exit fullscreen mode
Meagan is 28 years old and their favorite color is pink
Lauren is 25 years old and their favorite color is purple
Bob is 32 years old and their favorite color is green
Leonard is 7 years old and their favorite color is blue
Enter fullscreen mode Exit fullscreen mode

A splat removes the need for the explicit destructuring step:

def user_info(name, age, favorite_color)
  puts "#{name} is #{age} years old and their favorite color is #{favorite_color}"
end

users = [
  ["Meagan", 28, "pink"],
  ["Lauren", 25, "purple"],
  ["Bob", 32, "green"],
  ["Leonard", 7, "blue"]
]

users.each { |user| user_info(*user) }
Enter fullscreen mode Exit fullscreen mode
Meagan is 28 years old and their favorite color is pink
Lauren is 25 years old and their favorite color is purple
Bob is 32 years old and their favorite color is green
Leonard is 7 years old and their favorite color is blue
Enter fullscreen mode Exit fullscreen mode

Array Destructuring

I hinted at it above, but what the splat is doing is destructuring arrays. When you break an array down into individual elements, that is destructuring.

name, age, favorite_color = ["Meagan", 28, "pink"]

puts name
#=> Meagan
puts age
#=> 28
puts favorite_color
#=> pink
Enter fullscreen mode Exit fullscreen mode

Instead of naming every element in the array with a variable, we can use the splat operator.

Pop & Shift

The .pop and .shift method on Array in Ruby mutate the array they are invoked on. For example:

arr = [1, 2, 3, 4]
arr.shift
#=> 1
arr
#=> [2, 3, 4]
Enter fullscreen mode Exit fullscreen mode
arr = [1, 2, 3, 4]
arr.pop
#=> 4
arr
#=> [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

What if I want the first element, but I want to leave the array the way I found it?

arr = [1, 2, 3, 4]
first, *rest = arr
first
#=> 1
rest
#=> [2, 3, 4]
arr
#=> [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

If I just want the last element the splat goes at the beginning:

arr = [1, 2, 3, 4]
*first, last = arr
last
#=> 4
first
#=> [1, 2, 3]
arr
#=> [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

In either example, leave the splat unnamed if you only care about grabbing the first or last element.

arr = [1, 2, 3, 4]
first, * = arr
first
#=> 1
Enter fullscreen mode Exit fullscreen mode
arr = [1, 2, 3, 4]
*first, last = arr
last
#=> 4
Enter fullscreen mode Exit fullscreen mode

Other Array Use cases

You can also use a splat to flatten an array.

names = ["Meagan", "Lauren", "Leonard", "Bob"]
more_names = ["Alice", "John", *names, "Mary"]
more_names
Enter fullscreen mode Exit fullscreen mode
=> ["Alice", "John", "Meagan", "Lauren", "Leonard", "Bob", "Mary"]
Enter fullscreen mode Exit fullscreen mode

And you can use a splat to make an array.

arr = *"example"
#=> ["example"]

hsh ={a: "a", b: "b"}
splatted = *hsh
#=> [[:a, "a"], [:b, "b"]]
Enter fullscreen mode Exit fullscreen mode

Ruby language spec examples

The Ruby language spec is a great place to look to see how something works. Let's look at a few test cases that explain what a splat is doing. For the examples below, I'll be referencing this file. I'm not going to get too into the how of these tests or their setup. Please reference the documentation to learn more. It's a cool project.

context "with a single splatted Object argument" do
  before :all do
    def m(a) a end
  end

  it "does not call #to_ary" do
    x = mock("splat argument")
    x.should_not_receive(:to_ary)

    m(*x).should equal(x)
  end

  it "calls #to_a" do
    x = mock("splat argument")
    x.should_receive(:to_a).and_return([1])

    m(*x).should == 1
  end
end
Enter fullscreen mode Exit fullscreen mode

These tests show that x (the splatted argument) is turned into an array, by invoking .to_a on it (the should receive and return) when it is sent as a splatted argument to the m method (defined in the before :all block). Below is a test
that verifies the behavior we saw in the section "Invoking a method."

What would you expect this method invocation to return?

def m(a, b, *c, d, e)
  [a, b, c, d, e]
end

x = [1]
m(*x, 2, 3, 4)
Enter fullscreen mode Exit fullscreen mode

Hold that expectation in your head and let's look at what the corresponding test says:

 context "with a leading splatted Object argument" do
  before :all do
    def m(a, b, *c, d, e) [a, b, c, d, e] end
  end
  it "calls #to_a" do
    x = mock("splat argument")
    x.should_receive(:to_a).and_return([1])

    m(*x, 2, 3, 4).should == [1, 2, [], 3, 4]
  end

  ...
end
Enter fullscreen mode Exit fullscreen mode

c is turned into an empty array even though it came in the middle of the parameter list. Recall what we learned above, a splat can go anywhere in the parameter list, and Ruby is smart about resolving it. Because there are four
required arguments, *c doesn't gather up any of them into an array. There is no etc or rest.

What would happen if we invoked m with these arguments?

m(*x, 2, 3, 4, 5, 6, 7, 8, 9)
Enter fullscreen mode Exit fullscreen mode
#=> [1, 2, [3, 4, 5, 6, 7], 8, 9]
Enter fullscreen mode Exit fullscreen mode

Ruby knew it needed the last two arguments to assign to d and e. All the rest get shoveled into the array set to c.

I like to use the Ruby spec in tandem with the Ruby documentation. It's great to read about how something works, and reading test cases helps fill in my mental model even more.

Why would I use it?

You would use splat for a variety of reasons. If we look back at the Array Destructuring section, we can see how powerful it can be for building arrays without the need to coerce our types manually.
For some hash where there's a key of foo that has a value of some array, a typical use case sans splatting might look like:

  1. Checking that the array exists, and creating it if not
  2. Work regardless of someone passing in an array of strings or a single string

With splatting, that workflow looks like this:

hsh = {}

def add_foo
  ["bar", "baz"]
end

hsh[:foo] = [*hsh[:foo], *add_foo]
hsh
#=> {:foo=>["bar", "baz"]}

hsh = {}
hsh[:foo] = [*hsh[:foo], *"bar"]
#=> {:foo=>["bar"]}
Enter fullscreen mode Exit fullscreen mode

Real-World Examples

The sinatra gem uses splat args multiple times in their codebase. Here's one example from their test suite

def use(*args)
  @use << args
end

before do
  @use = []
end
Enter fullscreen mode Exit fullscreen mode

We don't need to get into the nitty-gritty of how they're using it in this particular use case. But we can see that the tests are setting up an empty array called @use, and they want to fill that array with elements, but they don't know how many, so use takes advantage of the splatted parameter.

Let's also look at thoughtbot's upcase source code. They're again using a splatted parameter in the test setup, and they're also invoking a method with a splatted argument.

it "doesn't render a CTA link" do
  render_trail completeables: []

  ...
end

# Creating a method with a splatted parameter
def render_trail(*trail_args)
  ...

  # Invoking a method with a splatted argument
  ...
  create_trail(*trail_args)
end

def create_trail(completeables:)
  ...

  completeables.each do |completeable|
    ...
  end

  ...
end
Enter fullscreen mode Exit fullscreen mode

In the above code, first render_trail is invoked with completeables: []. trail_args is a splatted parameter; if we were to inspect it, it would look like [{completeables: []}]. Next, we invoke create_trail with a splatted argument, meaning we explode out trail_args, and pass completeables: [] to create_trail.

These are just two examples I quickly found while looking through repositories I found. There are so many examples of libraries using splats to accomplish so many cool things.

Conclusion & Further Reading

I hope that splats are more approachable after reading this post and seeing the examples. This post is only a jumping-off point, and there is a lot more you can do and tons of ways that Rubyists use splats, including lots of compelling use cases in popular libraries like Rails. Take a look around in your favorite Ruby codebases, and let me know if you see any examples. The links below go further into what you can do with splats or provide more information.

While this was a pretty comprehensive overview of the splat, we haven't even scratched the surface of the double-splat yet; if you're interested in a blog post about that, please let me know by sending me a tweet.


  1. Asterisk wikipedia page ↩

Top comments (2)

Collapse
thorstenhirsch profile image
Thorsten Hirsch

Hash rocket? Really?

Collapse
meaganewaller profile image
Meagan Waller Author

Indeed! Kind of a silly name, but frequently used by Rubyists to refer to the fat arrow syntax.

🌚 Life is too short to browse without dark mode