Skip to content
AgileLogics
7 min read

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.
  • & rest collects the remaining items; :as all names 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]} binds a and b from the keys :a and :b — the form you'll use most.
  • :or gives defaults for missing keys; :as keeps 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.