In Progress
Unit 1, Lesson 21
In Progress

Splat Basics

Video transcript & code

When it's not slumming as the multiplication operator, the asterisk symbol serves a powerful role in Ruby, known as the "splat". There's a good chance you already use the splat in your day-to-day coding. If so, today's episode may be a review of things you already know. However, before I start making videos about advanced usage of splats I wanted to quickly cover the basics in order to make sure everyone is on the same page. So if you feel at all hazy on just what splatting is all about, keep watching.

Let's define an array of four symbols, named after their place in the array. Then we'll compose a new array, with the first array as the middle element.

a1 = [:first, :second, :third, :fourth]
a2 = [:before, a1, :after]
a2
# => [:before, [:first, :second, :third, :fourth], :after]

As we can see, the first array is now nested in the second. But what if what we really wanted to do was insert the contents of a1 into a2, not the array itself? That is, what if we wanted to end up with an array of five elements?

We could flatten a2 after the insertion, but a simpler way to do it is to use a splat. We prefix a1 with an asterisk, telling Ruby to "splat it out" into its component elements rather than inserting the array object. This time, the result is a array of five elements, with the contents of ai making up the middle three.

a1 = [:first, :second, :third, :fourth]
a2 = [:before, a1, :after] 
a2.flatten
# => [:before, :first, :second, :third, :fourth, :after]
a2 = [:before, *a1, :after]
# => [:before, :first, :second, :third, :fourth, :after]

This behavior, sometimes called "destructuring", is the essence of splatting.

A common use of destructuring is in assignment. You may know that Ruby supports multiple assignment. That is, if we put three variables called x, y, and z, separated by commas, on the left side of an assignment; and then three values on the right side, also separated by commas; the result will be that each variable is assigned the corresponding value from the right side of the assignment, in order.

x, y, z = 1, 2, 3
x                               # => 1
y                               # => 2
z                               # => 3

We can also splat a collection on the right side of an assignment, and have its contents broken out into separate variables on the left side. For instance, let's keep our x, y, z variables. But on the right side of the assignment we'll put our a1 array, with the splat operator in front of it. The result is that the first three elements of the array are assigned to x, y, and z respectively.

a1 = [:first, :second, :third, :fourth]
x, y, z = *a1
x                               # => :first
y                               # => :second
z                               # => :third

(By the way, if you're objecting right now that the splat operator isn't necessary in this case, rest assured we'll talk about implicit splatting in a future episode.)

The splatted collection doesn't have to be all alone on the right side of the assignment. Just as we expanded the contents of one array into another earlier, we can use the splat to expand a collection out into a list of values being assigned. For instance, we can add symbol to be assigned first, and then splat out our array to fill in the remaining variables on the left side.

a1 = [:first, :second, :third, :fourth]
x, y, z = :before, *a1
x                               # => :before
y                               # => :first
z                               # => :second

Splats aren't just for expanding collections on the right side of an assignment. They can also be used on the left side, to "slurp up" multiple values from the right side into a single variable. Let's try a few variations on this.

When we splat x, it absorbs the first two elements of the array, in the form of a new two-element array. When we splat y, it absorbs the middle two elements. And when we splat the final variable, it absorbs the trailing two elements. In each case, all of the other variables receive just a single element

a1 = [:first, :second, :third, :fourth]
*x, y, z = *a1
x                               # => [:first, :second]
y                               # => :third
z                               # => :fourth
x, *y, z = *a1
x                               # => :first
y                               # => [:second, :third]
z                               # => :fourth
x, y, *z = *a1
x                               # => :first
y                               # => :second
z                               # => [:third, :fourth]

Incidentally, this syntax gives us a concise way to grab the "first" and "rest" parts of a list, a common need when programming in a functional, recursive style. We can just use destructuring to assign the first element to one variable, and the rest two a second variable using a splat.

a1 = [:first, :second, :third, :fourth]
first, *rest = *a1

first                           # => :first
rest                            # => [:second, :third, :fourth]

As interesting as all this is, assignment is not the most common use of the splat. Where we usually see it in action is in method parameter lists, or in the argument list passed to a method.

For instance, let's say we have a method that sums three numbers, x, y, and z. If we have an array of numbers which we wish to sum, we can splat it out across the three parameters when we invoke the method.

def sum3(x, y, z)
  x + y + z
end

triangle = [90, 30, 60]
sum3(*triangle)                 # => 180

This is exactly like the splats we used on the right side of the assignment, earlier.

Conversely, we may have a method which should take a variable number of arguments. Here's one that takes a greeting followed by names of people to greet. By applying a splat to the last parameter, we force it to "soak up" all remaining arguments (if any) into a new array.

def greet(greeting, *names)
  names.each do |name|
    puts "#{greeting}, #{name}"
  end
end

greet("Good morning", "Grumpy", "Sneezy", "Dopey")
# >> Good morning, Grumpy
# >> Good morning, Sneezy
# >> Good morning, Dopey

This is exactly like when we used splats on the left side of assignment.

Everything we've seen so far holds true for writing and yielding to blocks, as well. We can write a method that yields a varying number of arguments to the provided block. For instance, here's a method that draws a specified number of random numbers, and it does that a specified number of times. Each time it draws, it yields the drawn numbers as individual arguments to the provided block using a splat.

def random_draw(num_times, num_draws)
  num_times.times do
    draws = num_draws.times.map { rand(10) }
    yield(*draws)
  end
end

random_draw(5, 3) do |first, second, third|
  puts "#{first} #{second} #{third}"
end
# >> 9 5 3
# >> 3 8 1
# >> 4 7 6
# >> 2 1 3
# >> 8 3 3

We can also use splats when passing a block. This is commonly used to collect trailing arguments into an array.

def random_draw(num_times, num_draws)
  num_times.times do
    draws = num_draws.times.map { rand(10) }
    yield(*draws)
  end
end

random_draw(5, 3) do |first, *rest|
  puts "#{first}; #{rest.inspect}"
end
# >> 7; [7, 0]
# >> 6; [6, 9]
# >> 6; [6, 2]
# >> 2; [2, 3]
# >> 3; [2, 2]

That about wraps it up for today. At this point you should have a handle on the fundamentals of the splat operator, if you didn't already. In future episodes we'll tackle more advanced usage. Until then, happy hacking!

Responses