In Progress
Unit 1, Lesson 21
In Progress

Destructuring

Video transcript & code

We've talked a bit about "destructuring" in the past, in episodes about "splatting" such as #80. Today I want to demonstrate an advanced example of destructuring.

Just to quickly review, in a Ruby context, destructuring assignment is the process of assigning multiple variables at one time, with each bound to a different portion of a data structure. A very simple example is assigning both items in a two-element array to variables at the same time. By using the splat operator, we explicitly tell Ruby to "break up" the array and distribute it across the variables on the left side of the assignment. When we then inspect their values, we can see that a has taken on the first array entry, and b has taken on the second.

arr = [2, 3]
(a, b) = *arr
a # => 2
b # => 3

As we've seen in other episodes such as #81, some of the syntax we've used here can be omitted in this simple case of splatting an array. But for the purpose of today's example I wanted to start out by illustrating the full, canonical syntax for destructuring.

Let us now turn our attention to a slightly more complex data structure. In the Rake build tool, dependencies are represented in the form of single-entry hashes. For instance, here's an example of the dependencies for a C program. The final executable, called hello, depends on a file called hello.c. It also depends on some supporting object files called foo.o and bar.o. If any of these files are updated, the executable needs to be recompiled in order to be current.

dep = {"hello" => ["hello.c", "foo.o", "bar.o"]}

This dependency follows a common convention for build tools: the first file in the dependency list is "special". It is the primary source file for the hello executable, whereas the others are supporting libraries that need to also be linked in at compile time.

In order to break out the component parts of this dependency, we could assign one piece at a time. In order to get the dependency target, we grab the first entry in the Hash. Individual hash entries are represented as two-element arrays, so we then take the first element in the resulting array.

The first prerequisite, then, is the first element of the last element of the first hash entry. And the rest of the prerequisites are the second through last elements of the last element of the first hash entry.

dep = {"hello" => ["hello.c", "foo.o", "bar.o"]}

dep.first                          # => ["hello", ["hello.c", "foo.o", "bar.o"]]
target = dep.first.first        # => "hello"
first_preq = dep.first.last.first # => "hello.c"
rest_preqs = dep.first.last[1..-1] # => ["foo.o", "bar.o"]

This code reads about as well as it sounds to describe it. Which is to say, it's complete gobbledygook. Let's try a different approach.

A Hash can be splatted into an Array. The result is an array of two-element arrays - in this case, just one two-element array. And we know from episode #84 that we can destructure nested arrays so long as we mimic the expected structure using parentheses on the left side of the assignment. So we can build up a destructuring assignment which has named slots for the dependency target, the first prerequisite, and all the remaining prerequisites. For the rest of the prerequisites, we make use of the fact that a variable number of elements can be "slurped" into a single variable by preceding it with a star.

Then, on the right side of the assignment, we splat out the dependency into an array, ready to be destructured.

When we examine the resulting variable assignments, we can see that we successfully captured the target, primary prerequisite, and remaining prerequisites. We did it all in a single assignment. And we were able to do it using a parenthesized form that visually echoes the "shape" of the data we are destructuring.

dep = {"hello" => ["hello.c", "foo.o", "bar.o"]}

a = *dep                        # => [["hello", ["hello.c", "foo.o", "bar.o"]]]

((target, (first_preq, *rest_preqs))) = *dep

target                          # => "hello"
first_preq                      # => "hello.c"
rest_preqs                      # => ["foo.o", "bar.o"]

And that's it for today. Happy hacking!

Responses