Destructuring
Prerequisites: let and local bindings, Maps
Destructuring pulls values out of collections right where you bind
names — in let, function parameters, loop, for, and doseq.
Sequential destructuring
(let [[a b c] [1 2 3]]
(println a b c))
(let [[first-item & others] ["x" "y" "z"]]
(println first-item others))
(let [[_ second-item] [:skip :keep]
[x y :as all] [10 20 30]]
(println second-item x y all))
(let [[[x1 y1] [x2 y2]] [[0 0] [3 4]]]
(println (Math/sqrt (+ (* (- x2 x1) (- x2 x1)) (* (- y2 y1) (- y2 y1))))))
(let [[a b c] [1]]
(println a b c))
1 2 3
x (y z)
:keep 10 20 [10 20 30]
5.0
1 nil nil
- A vector of names matches items by position.
& restcollects the remaining items;:as allnames the whole collection;_is a convention for "ignore this".- Patterns nest; missing items become
nil.
Map destructuring
(def user {:name "Ada" :email "ada@example.com" :roles [:admin]})
(let [{:keys [name email phone]} user]
(println name email phone))
(let [{:keys [name phone] :or {phone "unknown"} :as whole} user]
(println name phone (count whole)))
(let [{n :name [first-role] :roles} user]
(println n first-role))
(let [{:strs [host port]} {"host" "localhost" "port" 8080}]
(println host port))
Ada ada@example.com nil
Ada unknown 3
Ada :admin
localhost 8080
{:keys [a b]}bindsaandbfrom the keys:aand:b— the form you'll use most.:orgives defaults for missing keys;:askeeps the whole map.{local-name :key}binds one key to a different name, and the value can itself be destructured.:strs(string keys) and:syms(symbol keys) work like:keys.
In function parameters
(defn describe [{:keys [name age] :or {age "?"}}]
(str name " (" age ")"))
(defn distance [[x1 y1] [x2 y2]]
(Math/hypot (- x2 x1) (- y2 y1)))
(defn connect [host & {:keys [port timeout] :or {port 80 timeout 5000}}]
(str host ":" port " timeout=" timeout))
(println (describe {:name "Ada" :age 36}))
(println (describe {:name "Linus"}))
(println (distance [0 0] [3 4]))
(println (connect "example.com" :port 8443))
(println (connect "example.com" {:timeout 100}))
Ada (36)
Linus (?)
5.0
example.com:8443 timeout=5000
example.com:80 timeout=100
& {:keys [...]} accepts keyword arguments — and since Clojure 1.11
the caller may pass either loose key value pairs or a single map.
Qualified keys
(def order {:order/id 17 :order/total 99.5 :customer/name "Ada"})
(let [{:order/keys [id total] :customer/keys [name]} order]
(println id total name))
17 99.5 Ada
Practice
Write summary that takes a map like
{:title "Dune" :author {:first "Frank" :last "Herbert"} :year 1965} and
returns "Dune by Frank Herbert (1965)", using destructuring in the
parameter list.
Show solution
(defn summary [{:keys [title year] {:keys [first last]} :author}]
(str title " by " first " " last " (" year ")"))
(println (summary {:title "Dune" :author {:first "Frank" :last "Herbert"} :year 1965}))
Dune by Frank Herbert (1965)
Binding first and last shadows the core functions inside this
function only; in bigger functions pick different names to avoid
confusion.