In Clojure you can destructure a map using an arbitrary expression as the key.
For example, here kw is a local binding.
(let [kw :key
{a kw} {:key 1}]
a)
;=> 1
Usually this syntax is demonstrated as {sym0 :kw0 sym1 :kw1 ...},
which doesn’t reveal that the keywords are actually in expression position,
or an evaluation context.
The reason why this more recognizable syntax works is because keyword literals
are self-evaluating.
(let [{a :key} {:key 1}]
a)
;=> 1
The basic rule for expanding these expressions is:
(let [{binding expression} map])
=>
(let [binding (get map expression)])
So that code is equivalent to:
(let [a (get {:key 1} :key)]
a)
Symbols are not self-evaluating syntax in Clojure, so they must be quoted:
(let [{a 'key} {'key 1}]
a)
;=> 1
Applying the rule makes the need more obvious:
(let [a (get {'key 1} 'key)]
a)
Destructuring is pleasingly compositional. This ability to drop down to computed keys makes destructuring available in many more situations than if all keys were required to be statically declared. I found a few examples in my own code and other libraries where this flexibility has been useful.
An example that dereferences the var u/expr-type to compute the key:
(let [{cargs :args
res u/expr-type} (-> expr-noinline
ana2/unmark-top-level
ana2/unmark-eval-top-level
(check-expr expected opts))]
Another example that uses three class literals as computed keys:
(let [r (reflect-u/reflect cls)
{methods clojure.reflect.Method
fields clojure.reflect.Field
ctors clojure.reflect.Constructor
:as members}
(group-by
class
(filter (fn [{:keys [name] :as m}]
(if constructor-call
(instance? clojure.reflect.Constructor m)
(= m-or-f name)))
(:members r)))]
A snippet of code that destructures nested maps using a mix of keywords, quoted symbols and computed vectors-of-locals as keys. Notice that the vectors are in binding position sometimes to introduce names, then in expression position to perform lookups.
(let [...
{{[x1 x2] 'x} :fv
{[y1] 'y [z1 z2] 'z} :idx} remap
{{{[y1_x1 y1_x2] 'x
[y1_y1 y1_y2 y1_y3 y1_y4] 'y} [y1]
{[z1_x1] 'x
[z1_y1] 'y} [y1 z1]
{[z2_x1] 'x
[z2_y1] 'y} [y1 z2]} :idx-context} remap]
(is (= {:fv {'x [x1 x2]}
:idx {'y [y1]
'z [z1 z2]}
:idx-context {[y1] {'x [y1_x1 y1_x2]
'y [y1_y1 y1_y2 y1_y3 y1_y4]}
[y1 z1] {'x [z1_x1]
'y [z1_y1]}
[y1 z2] {'x [z2_x1]
'y [z2_y1]}}}
remap)))
You’ve probably seen code like this
that destructures booleans from a group-by:
(let [...
{anns false inits true} (group-by list? normalised-bindings)]
This Malli snippet nests :keys destructuring under a local binding key, method:
(-value-transformer [_ schema method options]
(reduce
(fn [acc {{:keys [name qname default transformers]} method}]
And this example elegantly destructures a nested map using keywords and locals, supporting the common pattern of updating a nested value in an atom then destructuring the swapped-in value’s relevant parts.
(defn remove-stale-cache-entries
[nsym ns-form-str sforms slurped opts]
{:pre [(simple-symbol? nsym)]}
(when ns-form-str
(let [{{{forms-cache ns-form-str} nsym} ::check-form-cache}
(env/swap-checker!
(env/checker opts)
update-in
[::check-form-cache nsym]
(fn [m]
(some-> m
(select-keys [ns-form-str])
not-empty
(update ns-form-str select-keys sforms))))]