Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map collections without turning them into lists

Is it possible to map collections without turning them into lists? I know about mapv for vectors, but what about sets or maps. Is there a generic map function that treats each collection as a functor (i.e. something mappable) and preserves its type after mapping?

Here is what i have mind vs whats happening now:

(map inc [1 2 3])         ; => [2 3 4], instead I get: (2 3 4)
(map inc #{1 2 3})        ; => #{3 2 4}, instead I get: (3 2 4)
(map inc {:a 1 :b 2 :c 3} ; => {:a 2 :b 3 :c 4}, instead I get: ClassCastException
like image 698
TomTom Avatar asked Jan 05 '23 00:01

TomTom


1 Answers

The return values from your functions aren't lists exactly, that's simply how they are represented in the REPL when printed. They are really instances of clojure.lang.LazySeq, as you can see here:

(class (map inc [1 2 3]))
=> clojure.lang.LazySeq

Rather than being mappable, Clojure attempts to deal with all collections as being Sequable. The Seq abstraction allows many transformations to be composed together into pipelines of operations on a collection, where maintaining the original input type often doesn't make sense, especially in terms of performance (speed). In this case, the dynamic nature of Clojure is actually quite useful.

It's trivial to convert the output of operations to the collection type that you want:

(-> (map inc [1 2 3])
    (vec))

or more simply, using into:

(into [] (map inc [1 2 3]))

into can be used for lists, maps and sets:

(into #{} (map inc #{1 2 3}))
=> #{4 3 2}

In your example where you attempt to map over a Hashmap, map is passed a sequence of key-value pairs in the form of a clojure.lang.MapEntry, so inc which expects a number, can't work on it directly.

You could do something like this, using destructuring to pull out the keys and values:

(into {} (for [[k v] {:a 1 :b 2 :c 3}]
           [k (inc v)]))
like image 60
Scott Avatar answered Jan 13 '23 20:01

Scott