2

I'd like to have a function/macro for checking a list to have truthy value eventually, and I hope the evaluation would be lazy. Here is my illustrative implementation without lazy evaluation:

(defn eventual [cols]
  (or (first cols) (if-let [rs (rest cols)]
                     (eventual rs))
                     false))

Here is a trivial example to illustrate:

(if (eventual [false (+ 1 2) (* 10000 10000)])
  true
  false)

I feel that there must be an implication with lazy evaluation. Maybe I'm just blinded at the moment. Please help to help. Thanks

Nathan Davis
  • 5,636
  • 27
  • 39
Yu Shen
  • 2,770
  • 3
  • 33
  • 48

2 Answers2

2

You can check if a sequence contains at least one truthy element with some function:

(some identity col)

If you pass it a lazy sequence as col it will evaluate its contents up to the first truthy element and won't realise the rest:

(let [col (take
            10
            (iterate
              #(do (println "Generating new value from " %) (inc %))
              0))]
  (some #(> % 5) col))

produces:

Generating new value from  0
Generating new value from  1
Generating new value from  2
Generating new value from  3
Generating new value from  4
Generating new value from  5
true

As you can see, values 6..9 are not produces at all.

You also should double check that the col you pass to some is really lazy and not already realised, because it might confuse you.

Piotrek Bzdyl
  • 12,965
  • 1
  • 31
  • 49
2

Your eventual function is as lazy as it can be. It searches eagerly for the first truthy item then stops. But it has problems:

  • It fails to terminate on an empty collection. (rest ()) is (), which is truthy. Use next instead of rest. (next ()) is nil, which is falsy.
  • It is truly recursive. It will blow the stack on a long enough search. Try (eventual (repeat false)). Since the recursion is tail-recursion, you can fix this by using recur in its place.
  • While we are at it, it is idiomatic to return nil, not false, upon running out of a collection. So drop the final false.

We end up with

(defn eventual [cols]
  (or (first cols) (if-let [rs (next cols)]
                     (recur rs))))

I'm a little queasy about what happens if cols is empty. Code based upon the source for some is clearer:

(defn eventual [coll]
    (when (seq coll)
      (or (first coll) (recur next coll))))

But using (some identity col), as Piotrek suggests, is probably best.

Community
  • 1
  • 1
Thumbnail
  • 13,293
  • 2
  • 29
  • 37