Destructuring trick?

Did you know that, when using associative destructuring in Clojure (not {:keys [my keys]}), you can use normal functions to compute the keys ?

(let [{value (inc 0)} {1 :a}] ;; (inc 0) => 1 => key in the map {1 :a}
  value)

Never seen this anywhere, I’ve just tried and it worked. I don’t know if it’s officially supported or not, good practice or not etc. What do you think ?

2 Likes

Strange. How is it computed?

My opinion, not sure:

(let [{value :a} {:a 1}]
  value)
;=> 1

I suppose that in the destructuring part {value :a}, the Clojure compiler says "Hey, I need the value for :a in the map {:a 1}: (get {:a 1} :a)". Since the destructuring syntax works like a normal “mini-let” binding (the key in the map {value :a} is the symbol to bind the value to), the value, like a let binding, can be any valid Clojure expression.

I think you can decompose the steps like this:

(let [{value (inc 0)} {1 :a}]
  value)
;=>
(let [value (get {1 :a} (inc 0)]
  value)
;=>
(let [value (get {1 :a} 1)]
  value)
;=>
(let [value :a]
  value)
;=>
:a
1 Like

In clojure let is a macro that transforms into a call to the let* special form after some preprocessing. This allows us to use macroexpand to find out what’s going on:

(macroexpand
 '(let [{value (inc 0)} {1 :a}] 
    value))
;;=>
(let* [map__14199 {1 :a}
       map__14199 (if (clojure.core/seq? map__14199)
                    (clojure.lang.PersistentHashMap/create (clojure.core/seq map__14199))
                    map__14199)
       value (clojure.core/get map__14199 (inc 0))]
  value)

… the funky gensym-generated names, like map__14199, are created by the destructure function, which is used by the various functions/macros that support this sort of thing. The source for that is in clojure.core if you want to have a look.

5 Likes

Nice trick. Didn’t notice let is a macro. I thought it’s a special form/

Me too. Of course, macroexpand. Thank you @jackrusher! Hopefully I was close to the truth.