tl;dr If you want a vector result, use mapv instead of map.

Much Clojure code involves the use of vectors. And why not? There’s a lot to love: immutabililty, quick random access, conj actually appends to the end. 😉

Vectors are so prevalent in Clojure code that there’s a version of map designed just for them: mapv. The practical difference is that, rather than receiving a lazy sequence, you get a fully realized vector. Notably, for the (mapv f coll) case specifically, mapv is also likely to be faster. Let’s take a look at the source for mapv to see why that is (I’ve left out the docstring ’n’ metadata):

(defn mapv
  ([f coll]
     (-> (reduce (fn [v o] (conj! v (f o))) (transient []) coll)
  ([f c1 c2]
     (into [] (map f c1 c2)))
  ([f c1 c2 c3]
     (into [] (map f c1 c2 c3)))
  ([f c1 c2 c3 & colls]
     (into [] (apply map f c1 c2 c3 colls))))

Ah! mapv uses transient to set up a mutable vector, then calls persistent! on the result to return a normal, immutable vector back to the caller. Transients are one of those nifty ways you can get your hands dirty with some mutation, while the outside world is none the wiser. Sometimes Clojure’s persistent data structures add non-trivial overhead, and this can be a useful optimization—but as with a lot of performance “hacks”, while it’s tempting to use this pattern everywhere, perhaps wait to see if there’s actually a discernible problem first.

It’s interesting to note that all other forms of mapv punt to regular map, calling into [] on the result. I mean, that’s how I mapv’d before I knew about mapv. Nice to get some validation for my life choices. 🙂

There’s also filterv, which does what you might expect.

Anyway! mapv: great for when you want a vector after a mapping operation.

Questions? Comments? Contact me!

Tools Used