Vector destructuring is the simplest form. It is similar to Python's list unpacking feature, but much more powerful.
Syntax: [symbol another-symbol] ["value" "another-value"]
(def my-vector [:a :b :c :d])
(def my-nested-vector [:a :b :c :d [:x :y :z]])
(let [[a b c d] my-vector]
(println a b c d))
;; => :a :b :c :d
(let [[a _ _ d [x y z]] my-nested-vector]
(println a d x y z))
;; => :a :d :x :y :z
You don't have to match the full vector.
(let [[a b c] my-vector]
(println a b c))
;; => :a :b :c
You can use & rest
to bind the remaining part of the vector to rest
.
(let [[a b & rest] my-vector]
(println a b rest))
;; => :a :b (:c :d)
When a destructuring form "exceeds" a vector (i.e. there not enough items in the vector to bind to), the excess symbols will be bound to nil
.
(let [[a b c d e f g] my-vector]
(println a b c d e f g))
;; => :a :b :c :d nil nil nil
You can use :as some-symbol
as the last two items in the destructuring form to bind the whole vector to some-symbol
(let [[:as all] my-vector]
(println all))
;; => [:a :b :c :d]
(let [[a :as all] my-vector]
(println a all))
;; => :a [:a :b :c :d]
(let [[a _ _ _ [x y z :as nested] :as all] my-nested-vector]
(println a x y z nested all))
;; => :a :x :y :z [:x :y :z] [:a :b :c :d [:x :y :z]]
You can use both & rest
and :as some-symbol
.
(let [[a b & rest :as all] my-vector]
(println a b rest all))
;; => :a :b (:c :d) [:a :b :c :d]
With destructuring and the & rest
form, you can specify optional arguments to functions.
(defn foo [a b & [x y z]]
(println a b x y z))
(foo :a :b) ;; => :a :b nil nil nil
(foo :a :b :x) ;; => :a :b :x nil nil
(foo :a :b :x :y :z) ;; => :a :b :x :y :z
Hashmap destructuring is much more complicated, and can get quite verbose, but is much more powerful than vector destructuring.
Syntax: {symbol :key, another-symbol :another-key} {:key "value" :another-key "another-value"}
(def my-hashmap {:a "A" :b "B" :c "C" :d "D"})
(def my-nested-hashmap {:a "A" :b "B" :c "C" :d "D" :q {:x "X" :y "Y" :z "Z"}})
(let [{a :a d :d} my-hashmap]
(println a d))
;; => A D
(let [{a :a, b :b, {x :x, y :y} :q} my-nested-hashmap]
(println a b x y))
;; => A B X Y
Similar to vectors, if a key is not found in the map, the symbol will be bound to nil
.
(let [{a :a, not-found :not-found, b :b} my-hashmap]
(println a not-found b))
;; => A nil B
You can provide an optional default value for these missing keys with the :or
keyword and a map of default values.
(let [{a :a, not-found :not-found, b :b, :or {not-found ":)"}} my-hashmap]
(println a not-found b))
;; => A :) B
The :as some-symbol
form is also available for hashmaps, but unlike vectors it can be specified anywhere (but still preferred to be the last two pairs).
(let [{a :a, b :b, :as all} my-hashmap]
(println a b all))
;; => A B {:a A :b B :c C :d D}
And combining :as
and :or
keywords (again, :as
preferred to be the last).
(let [{a :a, b :b, not-found :not-found :or {not-found ":)"} :as all} my-hashmap]
(println a b not-found all))
;; => A B :) {:a A :b B :c C :d D}
There is no & rest
for hashmaps.
Having to specify {symbol :symbol}
is repetitive and verbose (it's almost always going to be the symbol equivalent of the keyword), so there are shortcuts available so you only have to type the symbol once.
Here be dragons.