The Splat Operator
Tags: ruby
Published: 2020-12-15 14:55:19

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](https://gist.github.com/meaganewaller/515e4c6e20a6d3d27cea909f45cda4df).

## 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._

<!--more-->
## 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.

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

example("a", "b", "c", "d")
```
```text
a
["b", "c", "d"]
```

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:

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

example("a", 1, true, [1, 2, 3])
```
```text
a
[1, true, [1, 2, 3]]
```

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

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

example("a", 1, true, [1, 2, 3], "Hello")
```
```text
a
[1, true, [1, 2, 3]]
Hello
```
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?

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

example("a", "b", "c", "d")
```
```text
SyntaxError: unexpected *
def example(*a, *b)
                ^
```

### 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:

```ruby
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]) }
```
```text
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
```
A splat removes the need for the explicit destructuring step:

```ruby
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) }
```
```text
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
```

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

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

puts name
#=> Meagan
puts age
#=> 28
puts favorite_color
#=> pink
```

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:

```ruby
arr = [1, 2, 3, 4]
arr.shift
#=> 1
arr
#=> [2, 3, 4]
```

```ruby
arr = [1, 2, 3, 4]
arr.pop
#=> 4
arr
#=> [1, 2, 3]
```

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

```ruby
arr = [1, 2, 3, 4]
first, *rest = arr
first
#=> 1
rest
#=> [2, 3, 4]
arr
#=> [1, 2, 3, 4]
```
If I just want the last element the splat goes at the beginning:

```ruby
arr = [1, 2, 3, 4]
*first, last = arr
last
#=> 4
first
#=> [1, 2, 3]
arr
#=> [1, 2, 3, 4]
```

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

```ruby
arr = [1, 2, 3, 4]
first, * = arr
first
#=> 1
```
```ruby
arr = [1, 2, 3, 4]
*first, last = arr
last
#=> 4
```

### Other Array Use cases
You can also use a splat to flatten an array.

```ruby
names = ["Meagan", "Lauren", "Leonard", "Bob"]
more_names = ["Alice", "John", *names, "Mary"]
more_names
```
```text
=> ["Alice", "John", "Meagan", "Lauren", "Leonard", "Bob", "Mary"]
```

And you can use a splat to make an array.

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

hsh ={a: "a", b: "b"}
splatted = *hsh
#=> [[:a, "a"], [:b, "b"]]
```

### Ruby language spec examples

The [Ruby language spec](https://github.com/ruby/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](https://github.com/ruby/spec/blob/master/language/method_spec.rb). 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.

```ruby
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
```
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?

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

x = [1]
m(*x, 2, 3, 4)
```

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

```ruby
 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
```

`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?

```ruby
m(*x, 2, 3, 4, 5, 6, 7, 8, 9)
```
```text
#=> [1, 2, [3, 4, 5, 6, 7], 8, 9]
```

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:

```ruby
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"]}
```

### Real-World Examples

The [sinatra](https://github.com/sinatra/sinatra) gem uses splat args multiple times in their codebase. Here's one example from their [test suite](https://github.com/sinatra/sinatra/blob/8dc1e4ab2bec6be522ed2f700771ccf16a66c3c2/sinatra-contrib/spec/streaming_spec.rb#L16-L18)

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

before do
  @use = []
end
```

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](https://github.com/thoughtbot/upcase/blob/bb787c70d3182f458016a7d51d483bf70d932ad9/spec/views/trails/_incomplete_trail.html.erb_spec.rb#L15-L35). They're again using a splatted parameter in the test setup, and they're also invoking a method with a splatted argument.


```ruby
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
```
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.

- [What is the standalone splat operator(*) used for in Ruby?](https://stackoverflow.com/questions/42158005/what-is-the-standalone-splat-operator-used-for-in-ruby)
- [Naked asterisk parameters in Ruby](http://andrewberls.com/blog/post/naked-asterisk-parameters-in-ruby)
- [The Ruby method spec](https://github.com/ruby/spec/blob/master/language/method_spec.rb)
- [The Strange Ruby Splat](https://endofline.wordpress.com/2011/01/21/the-strange-ruby-splat/)
- [Fun with keyword arguments](https://www.justinweiss.com/articles/fun-with-keyword-arguments/)

[^1]: [Asterisk wikipedia page](https://en.wikipedia.org/wiki/Asterisk)


[^1]: [Asterisk wikipedia page](https://en.wikipedia.org/wiki/Asterisk)