clojure / clojure-site

clojure.org site
https://clojure.org
Eclipse Public License 1.0
249 stars 270 forks source link

Document destructuring of singleton map sequences #688

Open lassemaatta opened 7 months ago

lassemaatta commented 7 months ago

Since clojure 1.11 (as per CLJ-2603), destructuring a sequence containing a single map can directly bind to the map contents. An example of this can be seen here. It might be a good idea to mention this in the destructuring guide as this behaviour might be surprising to some (at least it was for me).

NoahTheDuke commented 4 months ago

here are some relevant threads on clojurian slack highlighting the confusion:

https://clojurians.slack.com/archives/C03S1KBA2/p1668533720466509

https://clojurians.slack.com/archives/C03S1KBA2/p1707982404402549

https://clojurians.slack.com/archives/C03S1KBA2/p1714580544135239

https://clojurians.slack.com/archives/C03S1KBA2/p1715935559111129

onetom commented 4 months ago

I just tripped over this issue today too.

i thought it's a bug, but @puredanger said it's intended behaviour: https://ask.clojure.org/index.php/12374/map-destructuring-works-on-singleton-lists?show=12380#c12380

so the Destructuring in Clojure guide should definitely mention these peculiarities!

it provides this example:

(defn configure [val & {:keys [debug verbose]
                        :or {debug false, verbose false}}]
  (println "val =" val " debug =" debug " verbose =" verbose))

so it should mention how it behaves the same way, as a function defined as:

(defn configure [val {:keys [debug verbose]
                      :or {debug false, verbose false}}]
  (println "val =" val " debug =" debug " verbose =" verbose))

when called as (configure 12 (list :debug true)) or (configure 12 (list {:debug true})), but not equivalent, when called as (configure 12 (vector :debug true)) or (configure 12 (vector {:debug true})).

im not sure what reasoning can be given for this behaviour though...

Pondering given this situation: ```clojure (let [m {:x 1} {itself :x} m {from-list :x} (list m) {from-list-of-2 :x} (list m m) {from-vector :x} (vector m)] [(= itself from-list) (nil? from-list-of-2) (not= from-list from-vector)]) ``` i'd say `(= itself from-list)` is surprising. `(nil? from-list-of-2)` looks like magic. `(not= from-list from-vector)` can easily result in hard to understand errors, since all u need is to realize some lazy sequence, by spilling it into a vector with `vec` or `(into [] ,,,)`, instead of `doall` and BAMM your program might blow up at a distance. in my specific situation the behaviour was even more magical, because i had function with an optional argument after the map destructuring one: `(fn [arg1 {:as arg2 k :some/k} & [arg3]] ,,,)`, yet it behaved like `(fn [arg1 & {:as arg2 k :some/k}] ,,,)`, which is apparently the same as `(fn [arg1 {:as arg2 k :some/k}] ,,,)`, when called with a map or a list of a single map.