Novice question, but I don't really understand why there are so many operations for constructing maps in clojure.
You have conj
, assoc
and merge
, but they seem to more or less do the same thing?
(assoc {:a 1 :b 2} :c 3)
(conj {:a 1 :b 2} {:c 3})
(merge {:a 1 :b 2} {:c 3})
What's really the difference and why are all these methods required when they do more or less the same thing?
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:
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 be conj
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 of MapEntry
s. Thus you can do e.g.
(into a-map (filter a-predicate another-map))
(note that into
uses conj
-- or conj!
, when possible -- internally). Neither merge
nor assoc
allows you to do that.
merge
:
This is almost exactly equivalent to conj
, but it replaces its nil
arguments with {}
-- empty hash maps -- and thus will return a map when the first "map" in the chain happens to be nil
.
(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 an IllegalArgumentException
. 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 I assoc
things onto maps more often than I conj
, though when I conj
, 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".
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 :
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With