(a promise-based approach from above, a core.async-based approach below. Both short circuits on the first false value.)
Here's a version using the fact that one promise can be delivered several times (although only the first delivery will succeed in setting its value, subsequent deliveries simply return nil without side effects).
(defn thread-and "Computes logical conjunction of return values of fs, each of which is called in a future. Short-circuits (cancelling the remaining futures) on first falsey value." [& fs] (let [done (promise) ret (atom true) fps (promise)] (deliver fps (doall (for [f fs] (let [p (promise)] [(future (if-not (swap! ret #(and %1 %2) (f)) (deliver done true)) (locking fps (deliver p true) (when (every? realized? (map peek @fps)) (deliver done true)))) p])))) @done (doseq [[fut] @fps] (future-cancel fut)) @ret))
Some tests:
(thread-and (constantly true) (constantly true)) ;;= true (thread-and (constantly true) (constantly false)) ;;= false (every? false? (repeatedly 100000 #(thread-and (constantly true) (constantly false)))) ;;= true ;; prints :foo, but not :bar (thread-and #(do (Thread/sleep 1000) (println :foo)) #(do (Thread/sleep 3000) (println :bar)))
Combining the ideas of Arthur and A. Webb, you can use core.async to and the results together in a short circuit when returning the first return value:
(defn thread-and "Call each of the fs on a separate thread. Return logical conjunction of the results. Short-circuit (and cancel the calls to remaining fs) on first falsey value returned." [& fs] (let [futs-and-cs (doall (for [f fs] (let [c (chan)] [(future (>!! c (f))) c])))] (loop [futs-and-cs futs-and-cs] (if (seq futs-and-cs) (let [[result c] (alts!! (map peek futs-and-cs))] (if result (recur (remove #(identical? (peek %) c) futs-and-cs)) (do (doseq [fut (map first futs-and-cs)] (future-cancel fut)) false))) true))))
Testing with (constantly false) and (constantly true) :
(thread-and (constantly true) (constantly true)) ;= true (thread-and (constantly true) (constantly false)) ;= false ;;; etc.
Also note that a short circuit really works:
;;; prints :foo before returning false (thread-and #(do (Thread/sleep 3000) false) #(do (Thread/sleep 1000) (println :foo))) ;;; does not print :foo (thread-and #(do (Thread/sleep 3000) false) #(do (Thread/sleep 7000) (println :foo)))
MichaΕ Marczyk
source share