Skip to content

Instantly share code, notes, and snippets.

@BrianLitwin
Last active June 26, 2019 09:28
Show Gist options
  • Save BrianLitwin/9d73c76d0388dffaefe826e8cf15679a to your computer and use it in GitHub Desktop.
Save BrianLitwin/9d73c76d0388dffaefe826e8cf15679a to your computer and use it in GitHub Desktop.

Two types of destructuring: sequential and associative

Sequential Destructuring

Sequential destructuring represents a sequential data structure as a Clojure vector within a let binding. This type of destructuring can be used on any kind of data structure that can be traversed in linear time, including lists, vectors, and anything that can produce a sequence.

(def my-vector [1 2 3])
(def my-list '(1 2 3))
(def my-string "abc")

;= It should come as no surprise that this will print out 1 2 3
(let [[x y z] my-vector]
  (println x y z))
;= 1 2 3

;= We can also use a similar technique to destructure a list
(let [[x y z] my-list]
  (println x y z))
;= 1 2 3

;= For strings, the elements are destructured by character.
(let [[x y z] my-string]
  (println x y z)
  (map type [x y z]))

The key to sequential destructuring is that you bind the values one-by-one to the symbols in the vector. For instance the vector [x y z] will match each element one-by-one with the list '(1 2 3).

In some cases, the collection you are destructuring isn’t the exact same size as the destructuring bindings. If the vector is too small, the extra symbols will be bound to nil. If the collection is too large, the extra values are simply ignored.


(def small-list '(1 2 3))
(let [[a b c d e f g] small-list]
  (println a b c d e f g))
;= 1 2 3 nil nil nil nil

(def large-list '(1 2 3 4 5 6 7 8 9 10))
(let [[a b c] large-list]
  (println a b c))
;= 1 2 3

Say you want to print the first element on one line and the remainder on another line.

(let [[item1 item2 item3 item4 item5 item6] names]
  (println item1)
  (println item2 item3 item4 item5 item6))
  
  
;; better way using & 
  
(let [[item1 & remaining] names]
  (println item1)
  (apply println remaining))
;= Michael
;= Amber Aaron Nick Earl Joe

You can ignore bindings that you don’t intend on using by binding them to any symbol of your choosing.

(let [[item1 _ item3 _ item5 _] names]
  (println "Odd names:" item1 item3 item5))
;= Odd names: Michael Aaron Earl

The convention for this is to use an underscore like above.

You can use :as all to bind the entire vector to the symbol all.


(let [[item1 :as all] names]
  (println "The first name from" all "is" item1))
;= The first name from [Michael Amber Aaron Nick Earl Joe] is Michael

:as vs & :

(def numbers [1 2 3 4 5])
(let [[x & remaining :as all] numbers]
  (apply prn [remaining all]))
;= (2 3 4 5) [1 2 3 4 5]

(def word "Clojure")
(let [[x & remaining :as all] word]
  (apply prn [x remaining all]))
;= \C (\l \o \j \u \r \e) "Clojure"

You can combine any or all of these techniques at the same time at your discretion.


(def fruits ["apple" "orange" "strawberry" "peach" "pear" "lemon"])
(let [[item1 _ item3 & remaining :as all-fruits] fruits]
  (println "The first and third fruits are" item1 "and" item3)
  (println "These were taken from" all-fruits)
  (println "The fruits after them are" remaining))
;= The first and third fruits are apple and strawberry
;= These were taken from [apple orange strawberry peach pear lemon]
;= The fruits after them are (peach pear lemon)

Destructuring can also be nested to get access to arbitrary levels of sequential structure.


(let [[[x1 y1][x2 y2]] my-line]
  (println "Line from (" x1 "," y1 ") to (" x2 ", " y2 ")"))
;= "Line from ( 5 , 10 ) to ( 10 , 20 )"
(def my-line [[5 10] [10 20]])

When you have nested vectors, you can use :as or & at any level as well.


(let [[[a b :as group1] [c d :as group2]] my-line]
  (println a b group1)
  (println c d group2))
;= 5 10 [5 10]
;= 10 20 [10 20]

Associative Destructuring

Associative destructuring is similar to sequential destructuring, but applied instead to associative (key-value) structures (including maps, records, vectors, etc). The associative bindings are concerned with concisely extracting values of the map by key.

The destructuring form is a map (instead of a vector, in sequential desctructuring).

The keys of the map are the symbols we want to bind in the let. The values of the destructuring map are the keys we will look up in the associative value. Here they are keywords (the most common case), but they could be any key value - numbers, strings, symbols, etc.

If you try to bind a key that is not present in the map, the binding value will be nil. but Associative destructuring also allows you to supply a default value if the key is not present in the associative value with the :or key.


(let [{category :category, :or {category "Category not found"}} client]
  (println category))
;= Category not found

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment