Welcome! Please see the About page for a little more info on how this works.

+1 vote
in Clojure by

I don't find the behavior of the following expression documented.
Can someone either point me to the documentation, or I request
that the documentation be updated to warn about this caveat.


((fn [& {:as key-vals}] key-vals) :a 100 :b 200) ;; case 1
(let [[& {:as key-vals}] '(:a 100 :b 200)] key-vals) ;; case 2

These both evaluate to a map {:a 100 :b 200). However, the following evaluate to nil rather than an empty map.

((fn [& {:as key-vals}] key-vals) ) ;; case 3
(let [[& {:as key-vals}] ()] key-vals)  ;; case 4

The tag https://clojure.org/guides/destructuring#_keyword_arguments does not exactly say what happens in these cases; however, intuitively I'd expect that key-vals should be a map in all 4 cases. Instead it is a map in the first to cases, but is nil in cases 3 and 4. I'd expect cases 3 and 4 to each evaluate to an empty map.

1 Answer

+2 votes

Destructuring is documented at https://clojure.org/reference/special_forms#binding-forms and says at the top "Binding-forms that don’t match their respective part due to an absence of data (i.e. too few elements in a sequential structure, no key in an associative structure, etc) bind to nil."

Same idea as:

(let [[a b] []] [a b])
;;=> [nil nil]
(let [[& es] []] es)
;;=> nil
(let [{:keys [a b], c :c} {}] [a b c])
;;=> [nil nil nil]

I will see if the guide needs to be clearer in this.

I added `:as opts` to those guide examples and showed what that binds to, which includes the "no kwargs" example that binds to nil.