Why are there so many map construction functions in clojure?
Solution 1:
assoc
and conj
behave very differently for other data structures:
user=> (assoc [1 2 3 4] 1 5)
[1 5 3 4]
user=> (conj [1 2 3 4] 1 5)
[1 2 3 4 1 5]
If you are writing a function that can handle multiple kinds of collections, then your choice will make a big difference.
Treat merge
as a maps-only function (its similar to conj
for other collections).
My opinion:
- assoc - use when you are 'changing' existing key/value pairs
- conj - use when you are 'adding' new key/value pairs
- merge - use when you are combining two or more maps
Solution 2:
Actually these functions behave quite differently when used with maps.
-
conj
:Firstly, the
(conj {:a 1 :b 2} :c 3)
example from the question text does not work at all (neither with 1.1 nor with 1.2;IllegalArgumentException
is thrown). There are just a handful of types which can beconj
ed onto maps, namely two-element vectors,clojure.lang.MapEntry
s (which are basically equivalent to two-element vectors) and maps.Note that
seq
of a map comprises a bunch ofMapEntry
s. Thus you can do e.g.(into a-map (filter a-predicate another-map))
(note that
into
usesconj
-- orconj!
, when possible -- internally). Neithermerge
norassoc
allows you to do that. -
merge
:This is almost exactly equivalent to
conj
, but it replaces itsnil
arguments with{}
-- empty hash maps -- and thus will return a map when the first "map" in the chain happens to benil
.(apply conj [nil {:a 1} {:b 2}]) ; => ({:b 2} {:a 1}) ; clojure.lang.PersistentList (apply merge [nil {:a 1} {:b 2}]) ; => {:a 1 :b 2} ; clojure.lang.PersistentArrayMap
Note there's nothing (except the docstring...) to stop the programmer from using
merge
with other collection types. If one does that, weirdness ensues; not recommended. -
assoc
:Again, the example from the question text --
(assoc {:a 1 :b 2} {:c 3})
-- won't work; instead, it'll throw anIllegalArgumentException
.assoc
takes a map argument followed by an even number of arguments -- those in odd positions (let's say the map is at position 0) are keys, those at even positions are values. I find that Iassoc
things onto maps more often than Iconj
, though when Iconj
,assoc
would feel cumbersome. ;-) -
merge-with
:For the sake of completeness, this is the final basic function dealing with maps. I find it extremely useful. It works as the docstring indicates; here's an example:
(merge-with + {:a 1} {:a 3} {:a 5}) ; => {:a 9}
Note that if a map contains a "new" key, which hasn't occured in any of the maps to the left of it, the merging function will not be called. This is occasionally frustrating, but in 1.2 a clever
reify
can provide a map with non-nil
"default values".
Solution 3:
Since maps are such a ubiquitous data structure in Clojure, it makes sense to have multiple tools for manipulating them. The various different functions are all syntactically convenient in slightly different circumstances.
My personal take on the specific functions you mention :
- I use assoc to add a single value to a map given a key and value
- I use merge to combine two maps or add multiple new entries at once
- I don't generally use conj with maps at all as I associate it mentally with lists