4

Is there a Clojure predicate that means "collection, but not a map"?

Such a predicate is/would be valuable because there are many operations that can be performed on all collections except maps. For example (apply + ...) or (reduce + ...) can be used with vectors, lists, lazy sequences, and sets, but not maps, since the elements of a map in such a context end up as clojure.lang.MapEntrys. It's sets and maps that cause the problem with those predicates that I know of:

  • sequential? is true for vectors, lists, and lazy sequences, but it's false for both maps and sets. (seq? is similar but it's false for vectors.)
  • coll? and seqable? are true for both sets and maps, as well as for every other kind of collection I can think of.

Of course I can define such a predicate, e.g. like this:

(defn coll-but-not-map?
  [xs]
  (and (coll? xs) 
       (not (map? xs))))

or like this:

(defn sequential-or-set?
  [xs]
  (or (sequential? xs)
      (set? xs)))

I'm wondering whether there's a built-in clojure.core (or contributed library) predicate that does the same thing.

This question is related to this one and this one but isn't answered by their answers. (If my question is a duplicate of one I haven't found, I'm happy to have it marked as such.)

Community
  • 1
  • 1
Mars
  • 8,689
  • 2
  • 42
  • 70
  • why wouldn't you know what data structure your function is getting? @Josh answered your yes/no question with "no" and offered some helpful advice. without more context on what you're trying to do, it's hard to help you. try multimethods? – Brandon Henry Mar 12 '17 at 14:05
  • Thanks for the questions @Brandon. I'm recursing into a map of maps (of maps (of maps ...)) in which the leaf nodes are either non-map collections of numbers, or something else such as a bare number. I want to calculate statistics on each collection of numbers, but not on the bare numbers. At present, the leaf collections are all sequences, but why not make the function more general, so that it will handle sets? Also see my comment on fl00r's answer. – Mars Mar 12 '17 at 17:10

2 Answers2

3

For example (apply + ...) or (reduce + ...) can be used with vectors, lists, lazy sequences, and sets, but not maps

This is nothing about collections, I think. In your case, you have a problem not with general apply or reduce application, but with particular + function. (apply + [:a :b :c]) won't work either even though we are using a vector here.

My point is that you are trying to solve very domain specific problem, that's why there is no generic solution in Clojure itself. So use any proper predicate you can think of.

fl00r
  • 82,987
  • 33
  • 217
  • 237
  • I understand your point, fl00r, but I think there is something that's common to all collections except maps: The elements of a map are in effect always `MapEntry`s. That's not true of any other kind of collection. Essentially, collections--except for maps--can treated as sets (or sequences, if order doesn't matter) of elements of an arbitrarily chosen type. `+` was just an illustration (see my comment in reply to Brandon's comment). I could make the same point with elements of other types. For example: There are no maps whose elements, when passed to `apply` or `reduce`, are strings. – Mars Mar 12 '17 at 17:14
  • @Mars I see. But again, from "sequence" point of view maps are lists of vectors (`MapEntry`s, but nevertheless). So your map `{:a 1 :b 2}` is essentially a list `([:a 1], [:b 2])`, right? I agree that maps are different in this way, but for this case `map?` predicate seems very natural and very explicit. – fl00r Mar 13 '17 at 12:50
  • I agree. `map?` will be enough for many cases. However, `(not (map? x))` is true for non-collections, so identifying a non-map collection requires conjunction or disjunction. I'm now thinking about whether I can make a case for adding an interface with this meaning to the language. I think it's a good idea, but it will be difficult to sell it to RH et al. – Mars Mar 13 '17 at 17:15
2

There's nothing that I've found or used that fits this description. I think your own predicate function is clear, simple, and easy to include in your code if you find it useful.

Maybe you are writing code that has to be very generic, but it's usually the case that a function both accepts and returns a consistent type of data. There are cases where this is not true, but it's usually the case that if a function can be the jack of all trades, it's doing too much.

Using your example -- it makes sense to add a vector of numbers, a list of numbers, or a set of numbers. But a map of numbers? It doesn't make sense, unless maybe it's the values contained in the map, and in this case, it's not reasonable for a single piece of code to be expected to handle adding both sequential data and associative data. The function should be handed something it expects, and it should return something consistent. This kind of reminds me of Stuart Sierra's blog post discussing consistency in this regard. Without more information I'm only guessing as to your use case, but it's something to consider.

Josh
  • 4,726
  • 2
  • 20
  • 32
  • Thanks Josh. Please see my comments in response to Brandon and fl00r. – Mars Mar 12 '17 at 17:20
  • 1
    Arbitrarily choosing one of the two answers as accepted. Somebody should get the points after all this time. – Mars Dec 20 '17 at 18:30