Why are there so many map construction functions in clojure?

Clojure

Clojure Problem Overview


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?

Clojure Solutions


Solution 1 - Clojure

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 - Clojure

Actually these functions behave quite differently when used with maps.

  1. 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 conjed onto maps, namely two-element vectors, clojure.lang.MapEntrys (which are basically equivalent to two-element vectors) and maps.

Note that seq of a map comprises a bunch of MapEntrys. 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.

  1. 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.

  1. 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. ;-)

  1. 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 - Clojure

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongrmView Question on Stackoverflow
Solution 1 - ClojuredbyrneView Answer on Stackoverflow
Solution 2 - ClojureMichał MarczykView Answer on Stackoverflow
Solution 3 - ClojuremikeraView Answer on Stackoverflow