Clojure Journey IX – Destructuring

While creating your beautiful functions you probably will face many times your function receiving a sequential-like structure and needing to decompose its values and bind it to names to use later. For example:

=> (def numbers [42 1 2 3 4])
#'user/numbers
=> (def my-func [any-vector]
     (println (first any-vector) (second any-vector)))
#'user/my-func
=> (my-func numbers)
42 1

You as a critical wizard, may think that’s awful, right? Would be nice if Clojure provides a way of do this process in a beautiful and magic way like in other features right?

This is so awful, we need a way to change it.

Fortunately clojure already provides a way to do it, and its called Destructuring,its the process of bind names to data structure’s values in a readable way.

Many languages have destructuring support like Javascript, Clojure as no exception, has it as feature and don’t require any third party libraries to do so.

A common point of confusion is to think that destructuring is pattern matching, but it’s not, it’s different. To achieve pattern matching in Clojure you can use core.match lib, but it’s topic for another post.

The basics

The most common example as we saw, is to extract things from vectors, imagine that inside your function, you need to bind names to each record of a size 2 vector, you probably will write something like that:

=> (defn print-values [vector]
     (println (first vector) (second vector)))
#'user/print-values
=> (print-values [1 2])
1 2

But you can use destructuring to do it in a less verbose and beautiful way, and it will work just as the first example.

=> (defn print-values [[first second]]
     (println first second))
#'user/print-values
=> (print-values [1 2])
1 2
List destructuring

Please, note that you don’t need to match the full vector, you can match only the first item if you want:

=> (defn print-values [[first-value]]
     (println first-value))
#'user/print-values
=> (print-values [1 2 3 4 5])
1

As in many languages you can use _ to ignore values, in destructuring you can use it to ignore values in the process:

=> (defn print-values [[first-value _ third-value]]
     (println first-value third-value))
#'user/print-values
=> (print-values [1 2 3])
1 3

You can use & to bind the remaining itens of the vector who are not bindend to any name.

=> (defn print-rest [[_ _ _ & rest-itens]]
     (println rest-itens))
#'user/print-rest
=> (print-rest [1 2 3 4 5 6 7])
(4 5 6 7)

Probably you’re thinking that may think that destructuring doesn’t make any sense, but when dealing with nested vectors, its beautiful (and will be much more beautiful when dealing with maps):

=> (defn print-nested [[_ _  [first-nested-value second-nested-value]]]
     (println first-nested-value second-nested-value))
#'user/print-nested
=> (print-nested [1 2 [3 4]])
3 4

Using the bind :as in the last thing of your destructuring you can bind all the struct to a name, just like this:

=> (defn entire-vector [[vec :as entire-vector]]
     (println entire-vector))
#'user/entire-vector
=> (entire-vector [1 2 3 4 5 6 7])
[1 2 3 4 5 6 7]

The beautiful way of dealing with maps

Most of Clojure developers works with web applications, and probably are working with JSON file format, meaning that most of them are working with maps. If you already worked if this kind of application you know that most of your time you’ll be dealing with this hash maps structures, getting data from it.

This brings us to a nice spot, where we can use what we learned from destructuring to extract what we want from a map, representing some received JSON. Let’s take as example, a map that represents a person:

=> (def people {:name "Otavio", 
                :last-name "Valadares",
                :contact {:phone "+550000000000",
                          :email "xpto@xpto.com"}, 
                :address {:country :brazil,
                          :state :sp,
                          :street "Xpto Street",
                          :number "42"}})
#'user/people

If we want, we can just access the number using techniques that we already learned in this post just changing to map, the recipe is to use the syntax {first-symbol :key, second-symbol :second-key}:

=> (defn address-number [{{number :number} :address}]
     (println number))
#'user/address-number
=> (address-number people)
42

But follow this syntax of always specify the symbol and key can be “ugly”, why not use a more beautiful and stylish way? Fortunately the first Clojure wizards has built a shortcut for us, we only need to use :keys key can be used to only write each key once.

=> (defn address-number [{{:keys [number]} :address}]
     (println number))
#'user/address-number
=> (address-number people)
42

It’s important to note that both lists and maps will bound nil when the value to destruct doesn’t exist, so, pay attention to it. But a good wizard always have a defense prepared against nil, and when dealing with destructuring, we can use :or keyword to define a default value to some bind. Just put the keyword fallowed by a map containing the not found key and the default value.

=> (defn get-doc [{:keys [doc] :or {doc"xxx"}}]
     (println doc))
#'user/get-document
=> (get-doc people)
xxx
Lets do some magic to have keywords!

Everything that we can use with lists, we can use with maps too, like :as or & keywords and much more.

Conclusions

Destructuring have many other tricks too, that you can study here. But with this text you already know enough to make your functions looks beautiful, it will look specially useful when we start dealing with JSON data with a lot of nested data.

Next steps

On the next post of this series, we’ll still talking about functions, with just a short trick to our functions.

Final thought

If you have any questions that I can help you with, please ask! Send an email (otaviopvaladares at gmail.com), pm me on my Twitter, or comment on this post!

Follow my blog to get notified every new post:

One thought on “Clojure Journey IX – Destructuring

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: