Anton Harald
Anton Harald

Reputation: 5954

Short Circuiting Truth Test of Predicate Collection

Say there is the need to check if an argument passes one truth test of a given predicate collection.

codewise:

(fn [x]
  (or (pred1 x) (pred2 x) (pred3 x) (pred4 x)))

due to the implementation of or, this short circuits after the first truthy value. As intended.

How can this be rewritten by using a collection of predicates:

[pred1 pred2 pred3 pred4]

A funky way would be:

(fn [x preds]
  (some?                                 ;; nil->false  
    (some true? (map #(% x) preds))))

It also turns out that this one does not short circuit. Might be due to Clojure's chunking of lazy sequences.

Can we do this better?

Upvotes: 1

Views: 158

Answers (4)

Edwin Dalorzo
Edwin Dalorzo

Reputation: 78639

Alternatively,

(defn somep? [x [p & ps :as preds]]
  (if-not (empty? preds)
    (or (p x) (somep? x ps))))

or

(defn somep? [x [p & ps :as preds]]
  (if-not (empty? preds)
    (let [res (p x)]
      (if-not res
        (recur x ps)
        res))))

Upvotes: 0

Mamun
Mamun

Reputation: 512

When I need short circuit, I use reduce with reduced.

(defn any-valid? [w & pred-fn-coll]
  (reduce (fn [v pf]
        (if (pf w)
          (reduced true)
          v)) false pred-fn-coll))

 (any-valid? 1 even? odd?)
 ;=> true
 (any-valid? 1 even? even?)
 ;=> false

Upvotes: 0

Thumbnail
Thumbnail

Reputation: 13483

I think it's map that's doing the chunking in your solution.

Try

(defn any-true? [preds]
  (fn [x]
    (loop [preds preds]
      (and (seq preds)
           (or ((first preds) x)
               (recur (rest preds)))))))

((any-true? [odd? even?]) 3)    ;true

((any-true? []) 3)              ;nil

((any-true? [even?]) 3)         ;nil

((any-true? [odd? #(/ % 0)]) 3) ;true

The last example shows that the evaluation is lazy.

Upvotes: 0

leetwinski
leetwinski

Reputation: 17849

clojure has a some-fn function for that:

user> ((some-fn true? false? nil?) true)
true
user> ((some-fn  false? nil?) true)
false

or for your case:

user> (defn any-pred? [x preds]
        ((apply some-fn preds) x))

another classic way is to do it recursively:

user> (defn any-pred? [x preds]
        (when-let [[pred & preds] (seq preds)]
          (or (pred x) (any-pred? x preds))))

user> (any-pred? true [false?])
nil
user> (any-pred? true [true?])
true
user> (any-pred? true [false? true?])
true
user> (any-pred? true [false? nil?])
nil
user> (any-pred? true [false? nil? true?])
true

Upvotes: 4

Related Questions