binding | since v0.0-927 | in clojure | Edit |
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 valueA 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
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
(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)))))