You can see what's going on under the covers by calling destructure
manually. Let's start with a simpler example:
user> (destructure ['{foo :foo} {:foo 42}])
[map__26147 {:foo 42}
map__26147 (if (clojure.core/seq? map__26147)
(clojure.lang.PersistentHashMap/create
(clojure.core/seq map__26147))
map__26147)
foo (clojure.core/get map__26147 :foo)]
This corresponds to (let [{foo :foo} {:foo 42}] ...)
(as you can verify with (macroexpand-1 '(let [{foo :foo} {:foo 42}] ...))
. The second line of the output is the important bit. A map binding form can work in two ways: if the value being bound is a seq, the seq will be 'poured' into a hash-map (as if by (apply hash-map the-seq)
. Otherwise, the value is assumed to be an associative and used directly. The seq 'pouring' feature was added in this commit.
Let's test this out:
user> (let [{foo :foo} {:foo 42}] foo)
42
user> (let [{foo :foo} (list :foo 42)] foo)
42
user> (let [{foo :foo} (apply hash-map (list :foo 42))] foo)
42
In the first case, the value is not a seq, so it's used directly. In the second case, a list is a seq, so it is 'poured' into a hash-map before being bound to {foo :foo}
. The third case shows that this pouring is semantically equivalent to (apply hash-map the-seq)
.
Now let's look at something like your example:
user> (destructure '[[& {:keys [foo bar]}] args])
[vec__26204 args
map__26205 (clojure.core/nthnext vec__26204 0)
map__26205 (if (clojure.core/seq? map__26205)
(clojure.lang.PersistentHashMap/create
(clojure.core/seq map__26205))
map__26205)
bar (clojure.core/get map__26205 :bar)
foo (clojure.core/get map__26205 :foo)]
The nthnext
bit is from the &
— in this case, because there are no fixed parameters before the &
, we have an (nthnext vec# 0)
, which amounts to just converting args
into a seq (if necessary). Then we have the map destructuring as above. Because the &
guarantees we have a seq, the seq special case for map destructuring will always be triggered, and the args will always be 'poured' into a hash-map before being bound to the map form.
In case the relationship between this example and your original fn is not clear, consider:
user> (macroexpand-1 '(fn [& {:keys [foo bar]}]))
(fn* ([& p__26214] (clojure.core/let [{:keys [foo bar]} p__26214])))