13

SQL offers a function called coalesce(a, b, c, ...) that returns null if all of its arguments are null, otherwise it returns the first non-null argument.

How would you go about writing something like this in Clojure?

It will be called like this: (coalesce f1 f2 f3 ...) where the fi are forms that should only be evaluated if required. If f1 is non-nil, then f2 should not be evaluated -- it may have side-effects.

Maybe Clojure already offers such a function (or macro).

EDIT: Here a solution that I came up with (modified from Stuart Halloway's Programming Clojure, (and ...) macro on page 206):

(defmacro coalesce
  ([] nil)
  ([x] x)
  ([x & rest] `(let [c# ~x] (if c# c# (coalesce ~@rest)))))

Seems to work.

(defmacro coalesce
  ([] nil)
  ([x] x)
  ([x & rest] `(let [c# ~x] (if (not (nil? c#)) c# (coalesce ~@rest)))))

Fixed.

Ralph
  • 31,584
  • 38
  • 145
  • 282

5 Answers5

25

What you want is the "or" macro.

Evaluates exprs one at a time, from left to right. If a form returns a logical true value, or returns that value and doesn't evaluate any of the other expressions, otherwise it returns the value of the last expression. (or) returns nil.

http://clojuredocs.org/clojure_core/clojure.core/or

If you only want nil and not false do a rewrite of and and name it coalesce.

Edit:

This could not be done as a function because functions evaluate all their arguments first. This could be done in Haskell because functions are lazy (not 100% sure about the Haskell thing).

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
nickik
  • 5,809
  • 2
  • 29
  • 35
  • I want the **first** non-nil value, not the last, however `(or ...)` does what I need. I did not realize that `(and ...)` and `(or ...)` return the values. I thought they returned false or true. But even these do not return the value I want for an input of `false`. – Ralph Nov 03 '10 at 12:24
  • oh sure im going to change that. – nickik Nov 03 '10 at 12:27
4

Based on nickik's answer and "or" clojure macro:

(defmacro coalesce
    ([] nil)
    ([x] x)
    ([x & next]
       `(let [v# ~x]
           (if (not (nil? v#)) v# (coalesce ~@next)))))
Arjan
  • 19,957
  • 2
  • 55
  • 48
3

You could use keep introduced in 1.2:

EDIT: extended answer a little bit. Macro for direct invokations. Helper for eg. apply + lazy seq producing the values.

(defn coalesce*
  [values]
  (first (keep identity values)))

(defmacro coalesce
  [& values]
  `(coalesce* (lazy-list ~@values)))

However to prevent evaluation of the values one needs some home-grown way.

Ugly:

(lazy-cat [e1] [e2] [e3])

A little more involved but prettier in the code:

(defn lazy-list*
  [& delayed-values]
  (when-let [delayed-values (seq delayed-values)]
    (reify
      clojure.lang.ISeq
      (first [this] @(first delayed-values))
      (next  [this] (lazy-list* (next delayed-values)))
      (more  [this] (or (next this) ())))))

(defmacro lazy-list
  [& values]
  `(lazy-list* ~@(map (fn [v] `(delay ~v)) values))
kotarak
  • 17,099
  • 2
  • 49
  • 39
  • I can see why a non-macro solution might be better, since the macro solution cannot be composed with other functions. – Ralph Nov 03 '10 at 13:17
  • @ralph: Of course the accepted solution is faster, but my solution is more flexible. What you should choose depends on your needs. If you don't need speed, but have a lazily created sequence you want to coalesce, my solution does the trick. If you need fast handling of few known values. then arjan's solution to the rescue. YMMV. :) – kotarak Nov 03 '10 at 14:19
  • I'm not criticizing. It is more of an academic exercise anyway. I was thinking about how to implement the "elvis" operator in Scala, and it got me thinking about something similar in Clojure. – Ralph Nov 03 '10 at 14:30
2

Some function versions of coalesce, if you'd rather avoid macros:

(defn coalesce
  "Returns first non-nil argument."
  [& args]
  (first (keep identity args)))

(defn coalesce-with
  "Returns first argument which passes f."
  [f & args]
  (first (filter f args)))

Usage:

=> (coalesce nil "a" "b")
"a"
=> (coalesce-with not-empty nil "" "123")
"123"

Unlike the spec, this will evaluate all args. Use or or another appropriate macro solution if you want short circuiting evaluation.

Brad Koch
  • 19,267
  • 19
  • 110
  • 137
0

Perhaps I'm misapprehending the question, but isn't this just the first filtered element?

E.g.:

user=> (first (filter (complement nil?) [nil false :foo]))
false
user=> (first (filter (complement nil?) [nil :foo]))
:foo
user=> (first (filter (complement nil?) []))
nil
user=> (first (filter (complement nil?) nil))
nil

It could be shortened up to:

(defn coalesce [& vals]
  (first (filter (complement nil?) vals)))
user=> (coalesce nil false :foo)
false
user=> (coalesce nil :foo)
:foo
user=> (coalesce nil)
nil
user=> (coalesce)
nil
Alex Taggart
  • 7,805
  • 28
  • 31
  • This is another seq-y way. A third one would be `(first (remove nil? ...))`. However this does not solve the problem that the expressions to be coalesced should only be evaluated on a per need basis. With things like `(repeatedly #(generate-something))` this works at of the box, but not for "literal" values: `[(do-something) (do-otherthing) (do-thirdthing)]`. Here everything is evaluated before the filter sees it. – kotarak Nov 04 '10 at 06:37
  • 1
    Yep, I missed the lazy-evaluation of args requirement. – Alex Taggart Nov 04 '10 at 16:38