Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure: Why is flatten "the wrong thing to use"

I've read this kind of thing a couple of times since I've started Clojure.

For instance, here: How to convert map to a sequence?

And in some tweet I don't remember exactly that was more or less saying "if you're using flatten you're probably doing it wrong".

I would like to know, what is wrong with flatten?

like image 593
Arnaud Avatar asked Aug 07 '14 12:08

Arnaud


3 Answers

I think this is what they were talking about in the answer you linked:

so> ((comp flatten seq) {:a [1 2] :b [3 4]})
(:b 3 4 :a 1 2)
so> (apply concat {:a [1 2] :b [3 4]})
(:b [3 4] :a [1 2])

Flatten will remove the structure from the keys and values, which is probably not what you want. There are use cases where you do want to remove the structure of nested sequences, and flatten was written for such cases. But for destructuring a map, you usually do want to keep the internal sequences as is.

like image 121
YosemiteMark Avatar answered Oct 23 '22 23:10

YosemiteMark


Anything flatten can't flatten, it ought to return intact. At the top level, it doesn't.

(flatten 8)
()

(flatten {1 2, 3 4})
()

If you think you've supplied a sequence, but you haven't, you'll get the effect of supplying an empty sequence. This is the sort of leg-breaker that most core functions take care to preclude. For example, (str nil) => "".

flatten ought to work like this:

(defn flatten [x]
  (if (sequential? x)
    ((fn flat [y] (if (sequential? y) (mapcat flat y) [y])) x)
    x))

(flatten 8)
;8

(flatten [{1 2, 3 4}])
;({1 2, 3 4})

(flatten [1 [2 [[3]] 4]])
;(1 2 3 4)

You can find Steve Miner's faster lazy version of this here.

like image 4
Thumbnail Avatar answered Oct 23 '22 23:10

Thumbnail


Probability of "probably"

Listen to people who say "you're probably doing it wrong", but also do not forget they say "probably", because it all depends on the problem.

For example if your task is to flatten the map where you could care less what was the key what was the value, you just need an unstructured sequence of all, then by all means, use flatten (or apply concat).

The reason it causes a "suspicion" is the fact that you had / were given a map to begin with, hence whoever gave it to you meant a "key value" paired structure, and if you flatten it, you lose that intention, as well as flexibility and clarity.

Keep in mind

In case you are still not sure what to do with a map for you particular problem, have a for comprehension in mind, since you would have a full control on what to do with the map as you iterate of it:

create a vector?

;; can also be (apply vector {:a 34 :b 42}), but just to use "for" for all consistently

user=> (into [] (for [[k v] {:a 34 :b 42}] [k v]))
[[:a 34] [:b 42]]

create another map?

user=> (into {} (for [[k v] {:a 34 :b 42}] [k (inc v)]))
{:a 35, :b 43}

create a set?

user=> (into #{} (for [[k v] {:a 34 :b 42}] [k v]))
#{[:a 34] [:b 42]}

reverse keys and values?

user=> (into {} (for [[k v] {:a 34 :b 42}] [v k]))
{34 :a, 42 :b}
like image 2
tolitius Avatar answered Oct 23 '22 22:10

tolitius