To preface, I am on Windows 7 (64-bit), running Java version 6 (update 33) using clooj as my IDE. I have not tried to reproduce my problem in any other system. I am experienced with Clojure, but not at all with Java.
The entirety of the problem I am trying to solve is lengthy to describe, but it boils down to this: let's say I would like to a make a macro that takes one argument, an associative map, and returns a vector of the elements of the map with their order conserved.
=>(defmacro vectorize-a-map
[associative-map]
(vec associative-map))
=>#'ns/vectorize-a-map
=>(vectorize-a-map {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8}
=>[[:a 1] [:b 2] [:c 3] [:d 4] [:e 5] [:f 6] [:g 7] [:h 8]]
That works, but add another element to the map and the order messes up...
=>(vectorize-a-map {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9}
=>[[:a 1] [:c 3] [:b 2] [:f 6] [:g 7] [:d 4] [:e 5] [:i 9] [:h 8]]
I believe I have discovered why this is happening. It seems like anything with 8 or fewer elements is instantiated as a PersistentArrayMap, which is exactly what I want, because from what I can tell, this class retains order. However, anything with 9 or more elements is instantiated as a PersistentHashMap, which does not retain order.
=>(type {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8}
=>clojure.lang.PersistentArrayMap
=>(type {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9}
=>clojure.lang.PersistentHashMap
I would like my macro to be able to take associative maps of any size, so this is a problem. I have tried type hinting, destructuring binding, for list comprehension, and unquote splicing, all without success. To draw it out, none of the following will work:
(defmacro vectorize-a-map
[^clojure.lang.PersistentArrayMap associative-map]
(vec associative-map))
(defmacro vectorize-a-map
[[& associative-map]]
(vec associative-map))
(defmacro vectorize-a-map
[associative-map]
(vec
(for [x associative-map]
x)))
(defmacro vectorize-a-map
[associative-map]
`(vector ~@associative-map))
With this toy problem I present, I realize I could simply write my macro like so, and avoid the problem altogether:
=>(defmacro vectorize-kvs
[& elements]
(vec (map vec (partition 2 elements))))
=>#'ns/vectorize-kvs
=>(vectorize-kvs :a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9)
=>[[:a 1] [:b 2] [:c 3] [:d 4] [:e 5] [:f 6] [:g 7] [:h 8] [:i 9]]
However, for the actual problem I am trying to solve (which I have not gotten into), it is important (although not 100% necessary) that the macro be able to take associative maps. It seems like I'm looking for how to cast the argument into a PersistentArrayMap before anything has a chance to happen to it. There may be some other avenue to a solution that I am simply not considering or aware of.
I've researched the best I've known how and haven't found anything helpful yet. Does anybody have any thoughts/advice?
you can make your map with array-map
or with a larger map
as a bonus you can avoid using a macro, which is useful because macros are not first-class and don't compose well*
a note on your first comment from the documentation on array map
If you find yourself depending on the order of keys in your maps you may want to consider if a
sorted-map
will get you what you need. It will scale better than anarray-map
. In the above example the output is the same:*this is my opinion
EDIT:
a time comparason of
sorted-map
vs.array-map
The problem as stated is not soluble. Maps are defined to have no order; any order you see in
array-map
is coincidental. If you require that your macro receive a map, you have already lost the information you desire.