destructure {}

bindingsince v0.0-927 in clojureEdit

Shorthand for destructuring a map into multiple names. Allows you to use a {...} pattern instead of a usual name in the following examples:

  • (defn [name]) -> (defn [{...}])
  • (let [name map]) -> (let [{...} map])
  • (for [name maps]) -> (for [{...} maps])

Quick pattern reference:

  • {name lookup} - name is bound using the lookup key
  • {:keys [...]} - names bound using keywords as lookup keys
  • {:strs [...]} - names bound using strings as lookup keys
  • {:syms [...]} - names bound using symbols as lookup keys
  • {:as name} - name is bound to whole value

Details:

A helpful shorthand for binding names to values inside a map.

The destructure map can be a map from a symbol to a lookup value:

(let [ {a :foo}   ;; <-- destructure map
       {:foo 1} ]
  a)
;;=> 1

The destructure map can bind multiple names:

(let [ {a :foo, b :bar}   ;; <-- destructure map
       {:foo 1, :bar 2} ]
  (println a b))
;; 1 2

Use this convenient alternative if names match the keys:

(let [ {:keys [foo bar]}   ;; <-- destructure map
       {:foo 1, :bar 2} ]
  (println foo bar))
;; 1 2

Different key types are supported using :keys, :strs, or :syms, which map to the manual destructuring forms below:

  • {:keys [foo]} -> {foo :foo }
  • {:strs [foo]} -> {foo "foo"}
  • {:syms [foo]} -> {foo 'foo }

Use :as foo to name the original value:

(let [ {:keys [foo bar] :as whole}
       {:foo 1, :bar 2} ]
  whole)
;;=> {:foo 1, :bar 2}

Use :or {} to provide default values if missing:

(let [ {:keys [foo bar] :or {bar 0} }
       {:foo 1} ]
  (println foo bar))
;; 1 0

Use the special destructuring map in place of any local name binding in the following forms:

  • (let [...])
  • (fn [...])
  • (loop [...])

Destructure maps can be nested, even in place of names in destructure vectors.

Also works for namespaced keys and symbols: https://github.com/clojure/clojure/blob/master/changes.md#22-map-destructuring-extended-to-support-namespaced-keys


Examples:

Use in place of function arguments:

(defn print-point
  [{:keys [x y z]}]
  (println x y z))

(print-point {:x 1, :y 2, :z 3})
;; 1 2 3

A non-vector sequence can be destructured as a map:

(let [{:keys [a b]} '(:a 1 :b 2)]
  (println a b))
;; 1 2

See Also:


Parser code @ clojurescript:src/main/clojure/cljs/core.cljc
(core/defn destructure [bindings]
  (core/let [bents (partition 2 bindings)
             pb (core/fn pb [bvec b v]
                  (core/let [pvec
                             (core/fn [bvec b val]
                               (core/let [gvec (gensym "vec__")
                                          gseq (gensym "seq__")
                                          gfirst (gensym "first__")
                                          has-rest (some #{'&} b)]
                                 (core/loop [ret (core/let [ret (conj bvec gvec val)]
                                                   (if has-rest
                                                     (conj ret gseq (core/list `seq gvec))
                                                     ret))
                                             n 0
                                             bs b
                                             seen-rest? false]
                                   (if (seq bs)
                                     (core/let [firstb (first bs)]
                                       (core/cond
                                         (= firstb '&) (recur (pb ret (second bs) gseq)
                                                              n
                                                              (nnext bs)
                                                              true)
                                         (= firstb :as) (pb ret (second bs) gvec)
                                         :else (if seen-rest?
                                                 (throw #?(:clj (new Exception "Unsupported binding form, only :as can follow & parameter")
                                                           :cljs (new js/Error "Unsupported binding form, only :as can follow & parameter")))
                                                 (recur (pb (if has-rest
                                                              (conj ret
                                                                    gfirst `(first ~gseq)
                                                                    gseq `(next ~gseq))
                                                              ret)
                                                            firstb
                                                            (if has-rest
                                                              gfirst
                                                              (core/list `nth gvec n nil)))
                                                        (core/inc n)
                                                        (next bs)
                                                        seen-rest?))))
                                     ret))))
                             pmap
                             (core/fn [bvec b v]
                               (core/let [gmap (gensym "map__")
                                          defaults (:or b)]
                                 (core/loop [ret (core/-> bvec (conj gmap) (conj v)
                                                          (conj gmap) (conj `(--destructure-map ~gmap))
                                                     ((core/fn [ret]
                                                        (if (:as b)
                                                          (conj ret (:as b) gmap)
                                                          ret))))
                                             bes (core/let [transforms
                                                            (reduce
                                                              (core/fn [transforms mk]
                                                                (if (core/keyword? mk)
                                                                  (core/let [mkns (namespace mk)
                                                                        mkn (name mk)]
                                                                    (core/cond (= mkn "keys") (assoc transforms mk #(keyword (core/or mkns (namespace %)) (name %)))
                                                                               (= mkn "syms") (assoc transforms mk #(core/list `quote (symbol (core/or mkns (namespace %)) (name %))))
                                                                               (= mkn "strs") (assoc transforms mk core/str)
                                                                               :else transforms))
                                                                  transforms))
                                                              {}
                                                              (keys b))]
                                                   (reduce
                                                     (core/fn [bes entry]
                                                       (reduce #(assoc %1 %2 ((val entry) %2))
                                                         (dissoc bes (key entry))
                                                         ((key entry) bes)))
                                                     (dissoc b :as :or)
                                                     transforms))]
                                   (if (seq bes)
                                     (core/let [bb (key (first bes))
                                                bk (val (first bes))
                                                local (if #?(:clj  (core/instance? clojure.lang.Named bb)
                                                             :cljs (cljs.core/implements? INamed bb))
                                                          (with-meta (symbol nil (name bb)) (meta bb))
                                                        bb)
                                                bv (if (contains? defaults local)
                                                     (core/list 'cljs.core/get gmap bk (defaults local))
                                                     (core/list 'cljs.core/get gmap bk))]
                                       (recur
                                         (if (core/or (core/keyword? bb) (core/symbol? bb)) ;(ident? bb)
                                           (core/-> ret (conj local bv))
                                           (pb ret bb bv))
                                              (next bes)))
                                     ret))))]
                    (core/cond
                      (core/symbol? b) (core/-> bvec (conj (if (namespace b) (symbol (name b)) b)) (conj v))
                      (core/keyword? b) (core/-> bvec (conj (symbol (name b))) (conj v))
                      (vector? b) (pvec bvec b v)
                      (map? b) (pmap bvec b v)
                      :else (throw
                             #?(:clj (new Exception (core/str "Unsupported binding form: " b))
                                :cljs (new js/Error (core/str "Unsupported binding form: " b)))))))
             process-entry (core/fn [bvec b] (pb bvec (first b) (second b)))]
    (if (every? core/symbol? (map first bents))
      bindings
      (core/if-let [kwbs (seq (filter #(core/keyword? (first %)) bents))]
        (throw
          #?(:clj (new Exception (core/str "Unsupported binding key: " (ffirst kwbs)))
             :cljs (new js/Error (core/str "Unsupported binding key: " (ffirst kwbs)))))
        (reduce process-entry [] bents)))))