Why only vectors `into` map?

Why does into {} fail when given a list, but succeed with a vector?

(let [cols [:id :label]
      data '({:id 1 :label "one"} {:id 2 :label "two"})]
;;(into {} (map #(map % cols) data)) ;; Execution error (ClassCastException) at (REPL:1).
  (into {} (map #(mapv % cols) data))
  ;; {1 "one", 2 "two"}
  ) 

The behavior of conj with maps is:

  1. when provided a MapEntry it adds it to the map (as in associates key and value)
  2. when provided a vector, same as 1) but it treats the 0th position of the vector as the key and the 1st position as the value to be associated with the map
  3. when provided other seqables than a vector, it treats it as a seqable of MapEntries, so it’s a repeated version of 1). If the seqable does not contain MapEntries then you’ll run into a ClassCastException.

The rationale for 2 could be that creating vectors is much more ergonomic than having to create MapEntry instances.

The rationale for 3 could be that these kinds of operations work:

(conj {:a 1} (filter (fn [[k v]] (odd? v)) {:b 2 :c 3}))
;;=> {:a 1, :c 3}

Now I’ll wait for Alex to give the official answer :P.

3 Likes

LGTM borkdude :slight_smile:

I guess the only thing to add is that a list of 2 elements is not treated as a valid map entry because it is not indexed (you can’t just grab the 0th key “slot” and the 1th value “slot”), you would have to iterate through the key to get to the value.

4 Likes

Ah; so lists fail ducktyping as vectors so are handled differently on map conj. I can remember that. Thanks!