tags:

views:

239

answers:

3

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})

Whats really the difference and why are all these methods required when they do more or less the same thing?

+3  A: 

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
mikera
I think the idiomatic approach would be to use whichever function matches the form in which the new items to be added to the map initially become available; if it's as maps, use `merge`, if as a bunch of keys and values not assembled in a collection, use `assoc` etc. What I really wanted to point out, though, is that `conj` is *the* universal Clojure data structure building function -- you can't really keep it in the list drawer.
Michał Marczyk
@Michal maybe you are right - but for some reason I have a distrust of functions that do semantically very different things on different input types without warnings :-)
mikera
@mikera I would argue that `conj` **does** do semantically equivalent operations on different input types. ;)
dbyrne
+15  A: 

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
dbyrne
`merge` takes an arbitary amount of maps and merges them, not just two.
ponzao
Good point. Changed my wording.
dbyrne
No prob, +1 for a good answer.
ponzao
`merge` is different from `conj` in its handling of `nil` (which gets converted to `{}`), regardless of the types of other arguments. Also, I have a different approach to the `assoc` vs. `conj` issue, but since I've posted a separate answer, I won't elaborate here. :-)
Michał Marczyk
+6  A: 

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.

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

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

  4. 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".

Michał Marczyk
To illustrate the final point on `merge-with`, I've prepared the following Gist: http://gist.github.com/468332
Michał Marczyk
Thanks for the explanations. As you saw I had a typing error and switched the second argument between conj and assoc.
grm