Search code examples
clojuredestructuring

How to return value for empty collection passed to Clojure function


I have some function that returns greatest value for some key from passed maps. I want to return 0 when passed collection is empty. Of course I can make it using some conditional, but wondering if there is some more advanced technique for it?

(defn max-id [c]
  "Using as: (max-id [{ :id 1 }, { :id 2 }])"
  (if (empty? c)
    0
    (apply max (map :id c))))
;;;
(max-id []) => 0
(max-id [map-one map-two]) => 1024

Solution

  • Ironically, "advanced" is something that most advanced programmers tend to avoid when it is possible to do so, usually opting for simplicity when they can. The bottom line here is that your function works great, because of one reason: your intent is clear. Everyone can understand what happens if an empty collection is passed in, even if they are not a programmer. This is a huge benefit, and makes your solution already an ideal one, at least as far as the empty collection case goes.

    Now, if you can guarantee a constraint that at least one of the :ids in a given collection will be zero or greater (i.e., we're dealing with positive ids), then you can do:

    (defn max-id [c]
      (reduce max 0 (map :id c)))
    

    However, this does not work if you have a list of maps which all have a negative id, i.e.: [{:id -1} {:id -2}], as in this case the max-id should be -1, and so the above does not yield the correct answer.

    There are other solutions, but they involve a conditional check, even if it's a little more implicit. For example:

    (defn max-id [c]
      (apply max (or (not-empty (map :id c)) [0])))
    

    One option that I wouldn't recommend involves replacing max with a function which takes 0 or more args, checks to see whether there are any args, calls max if there are, and returns 0 if there are not. Unless you have some other use case that would require the max of nothing to be zero, I would avoid doing this, so I'm not even going to post it. This becomes overly complicated, and requires readers to go digging, so just don't do this.

    As an aside, there is a core function that is not very well known that is arguably more idiomatic for finding the element whose value is largest for a given key, in a list. It is called max-key:

    (defn max-id [c]
      (:id (apply max-key :id c)))
    

    However, I'd say that your function is easier to understand, if for no other reason than max-key is not as well known as max and map. So, just giving it here as an additional option for you.