The Hyperpessimist

The grandest failure.

Destructuring Order in Or-clauses

Lately I was wondering how to set variables in a specific order in a :or clause in Clojure’s destructuring mini language. I needed to set a key b to the return value of a function f that is dependant on a key a.

My first thought was that the :or {} syntax wouldn’t obey the order (since it is a map), so I’ve used :or []. But that doesn’t work at all, it does not do anything, both get bound to nil, as if no :or was specified.

When using :or {} it magically worked, but how can you trust that the order is always correct, since maps are inherently unordered?

Turns out, the order in which keys are taken from the :or map depends on the :keys vector! Compare specifying a before b, which works as expected:

1
2
3
4
5
6
7
8
9
10
11
12
13
(destructure '[{:keys [a b] :or {a x b (f a)}} x])
;=>
[map__39123
 x
 map__39123
 (if
  (clojure.core/seq? map__39123)
  (clojure.lang.PersistentHashMap/create (clojure.core/seq map__39123))
  map__39123)
 a
 (clojure.core/get map__39123 :a x)
 b
 (clojure.core/get map__39123 :b (f a))]

with specifying b before a which fails since a is not yet known.

1
2
3
4
5
6
7
8
9
10
11
12
13
(destructure '[{:keys [b a] :or {a x b (f a)}} x])
;=>
[map__39117
 x
 map__39117
 (if
  (clojure.core/seq? map__39117)
  (clojure.lang.PersistentHashMap/create (clojure.core/seq map__39117))
  map__39117)
 b
 (clojure.core/get map__39117 :b (f a))
 a
 (clojure.core/get map__39117 :a x)]

This behaviour works well but it was rather unexpected. It kinda does explain why the :keys takes a vector as a value and not a set, because the order is important.

Thanks to Jan Stępień for getting to the bottom of this.