;; clojure.core.async — higher-level dataflow API over the channel primitives. ;; ;; The primitives (chan, !, !!, close!, put!, take!, offer!, timeout, ;; promise-chan, buffer/dropping-buffer/sliding-buffer, go/go-loop/thread, go-spawn) ;; are provided natively (host/chez/java/async.ss) on real OS threads. This overlay ;; adds the portable dataflow operators — alts!, pipe, pipeline, split, reduce, ;; transduce, mult, mix, pub/sub, map, merge, and the deprecated map/… — ;; ported from clojure.core.async over those primitives. Because go blocks are real ;; threads, parking ops are ordinary blocking ops and work anywhere; this is a ;; superset of the JVM model (no fixed thread pool, no pending-op limit). (ns clojure.core.async (:refer-clojure :exclude [reduce transduce into merge map take partition partition-by])) ;; --- alts ------------------------------------------------------------------- ;; do-alts polls each port non-blockingly under its own channel lock; the first ;; ready op wins. A take port is ready when a value (or closed nil) is available; ;; a put spec [ch val] is ready when the value can be offered. Polls with a 1ms ;; backoff (no cross-channel wait-set). (defn- alt-attempt [port] (if (vector? port) (let [ch (nth port 0) v (nth port 1)] (assert (some? v) "Can't put nil on channel") (let [r (clojure.core.async/__offer! ch v)] ; true | false (closed) | nil (would block) (when (some? r) [r ch]))) (let [r (clojure.core.async/__poll! port)] ; value | nil (closed) | ::none (when (not= r ::none) [r port])))) (defn do-alts "Returns [val port] for the first ready op among ports. ports is a vector of take ports and/or [channel val] put specs. opts may include :priority true (try in order) and :default val (return [val :default] if none ready)." [ports opts] (assert (pos? (count ports)) "alts must have at least one channel operation") (let [ports (vec ports) n (count ports) priority (:priority opts) has-default (contains? opts :default)] ;; Scan ports from a random start (sequential, wrapping) so a non-priority alts ;; is fair without allocating a fresh shuffle every poll. With :priority the scan ;; starts at 0 (declared order). Returns the first ready op. (loop [first? true] (let [start (if priority 0 (rand-int n)) hit (loop [k 0] (when (< k n) (let [j (+ start k) i (if (< j n) j (- j n))] (or (alt-attempt (nth ports i)) (recur (inc k))))))] (cond hit hit (and first? has-default) [(:default opts) :default] :else (do (Thread/sleep 1) (recur false))))))) (defn alts!! "Completes at most one of several channel operations. ports is a vector of take ports and/or [channel val] put specs. Returns [val port]. Blocks until ready." [ports & {:as opts}] (do-alts ports opts)) (defn alts! "Like alts!!. In jolt a go block is a real thread, so parking and blocking alts are the same operation." [ports & {:as opts}] (do-alts ports opts)) (defn poll! "Takes a val from port if possible immediately. Never blocks. Returns the value or nil." [port] (let [r (clojure.core.async/__poll! port)] (when (not= r ::none) r))) ;; --- thread variants -------------------------------------------------------- (defn thread-call "Executes f in another thread, returning a channel that receives f's result then closes." ([f] (clojure.core.async/go-spawn f)) ([f _workload] (clojure.core.async/go-spawn f))) (defmacro io-thread "Executes body in another thread, returning a channel that receives the result then closes." [& body] `(thread-call (fn [] ~@body) :io)) ;; --- pipe / pipeline -------------------------------------------------------- (defn pipe "Takes elements from the from channel and supplies them to the to channel. Closes to when from closes unless close? is false." ([from to] (pipe from to true)) ([from to close?] (go-loop [] (let [v (! to v) (recur))))) to)) (defn- pipeline* [n to xf from close? ex-handler type] (assert (pos? n)) (let [jobs (chan n) results (chan n) process (fn [job] (if (nil? job) (do (close! results) nil) (let [v (nth job 0) p (nth job 1) res (chan 1 xf ex-handler)] (>!! res v) (close! res) (put! p res) true))) afn (fn [job] (if (nil? job) (do (close! results) nil) (let [v (nth job 0) p (nth job 1) res (chan 1)] (xf v res) (put! p res) true)))] (dotimes [_ n] (case type (:blocking :compute) (thread (loop [] (let [job (! jobs [v p]) (>! results p) (recur))))) (go-loop [] (let [p (! to v)) (recur)))) (recur))))))) (defn pipeline "Takes elements from from, applies transducer xf with parallelism n, supplies to to. Outputs are ordered relative to inputs." ([n to xf from] (pipeline n to xf from true)) ([n to xf from close?] (pipeline n to xf from close? nil)) ([n to xf from close? ex-handler] (pipeline* n to xf from close? ex-handler :compute))) (defn pipeline-blocking "Like pipeline, for blocking operations." ([n to xf from] (pipeline-blocking n to xf from true)) ([n to xf from close?] (pipeline-blocking n to xf from close? nil)) ([n to xf from close? ex-handler] (pipeline* n to xf from close? ex-handler :blocking))) (defn pipeline-async "Like pipeline, for async fns af of two args [input result-channel]." ([n to af from] (pipeline-async n to af from true)) ([n to af from close?] (pipeline* n to af from close? nil :async))) (defn split "Splits ch by predicate p into [true-chan false-chan]." ([p ch] (split p ch nil nil)) ([p ch t-buf-or-n f-buf-or-n] (let [tc (chan t-buf-or-n) fc (chan f-buf-or-n)] (go-loop [] (let [v (! (if (p v) tc fc) v) (recur))))) [tc fc]))) ;; --- reduce / transduce / collection sinks ---------------------------------- (defn reduce "Returns a channel with the single result of reducing ch with f from init." [f init ch] (go-loop [ret init] (let [v (! ch (first vs))) (recur (next vs)) (when close? (close! ch)))))) (defn to-chan! "Returns a channel containing the contents of coll, closing when exhausted." [coll] (let [c (bounded-count 100 coll)] (if (pos? c) (let [ch (chan c)] (onto-chan! ch coll) ch) (let [ch (chan)] (close! ch) ch)))) (defn onto-chan!! "Like onto-chan! for use when accessing coll might block." ([ch coll] (onto-chan!! ch coll true)) ([ch coll close?] (thread (loop [vs (seq coll)] (if (and vs (>!! ch (first vs))) (recur (next vs)) (when close? (close! ch))))))) (defn to-chan!! "Like to-chan! for use when accessing coll might block." [coll] (let [c (bounded-count 100 coll)] (if (pos? c) (let [ch (chan c)] (onto-chan!! ch coll) ch) (let [ch (chan)] (close! ch) ch)))) (defn onto-chan "Deprecated - use onto-chan! or onto-chan!!" ([ch coll] (onto-chan! ch coll true)) ([ch coll close?] (onto-chan! ch coll close?))) (defn to-chan "Deprecated - use to-chan! or to-chan!!" [coll] (to-chan! coll)) (defn into "Returns a channel with the single collection result of conjoining items from ch onto coll. ch must close first." [coll ch] (reduce conj coll ch)) (defn take "Returns a channel that returns at most n items from ch, then closes." ([n ch] (take n ch nil)) ([n ch buf-or-n] (let [out (chan buf-or-n)] (go (loop [x 0] (when (< x n) (let [v (! out v) (recur (inc x)))))) (close! out)) out))) ;; --- mult / tap ------------------------------------------------------------- (defprotocol Mux (muxch* [_])) (defprotocol Mult (tap* [m ch close?]) (untap* [m ch]) (untap-all* [m])) (defn mult "Creates a mult of ch. Copies can be created with tap and removed with untap. Each item is distributed to all taps synchronously." [ch] (let [cs (atom {}) m (reify Mux (muxch* [_] ch) Mult (tap* [_ ch close?] (swap! cs assoc ch close?) nil) (untap* [_ ch] (swap! cs dissoc ch) nil) (untap-all* [_] (reset! cs {}) nil)) dchan (chan 1) dctr (atom nil) done (fn [_] (when (zero? (swap! dctr dec)) (put! dchan true)))] (go-loop [] (let [val (! out v) (recur state)) (recur state))))) m)) (defn admix "Adds ch as an input to the mix." [mix ch] (admix* mix ch)) (defn unmix "Removes ch as an input to the mix." [mix ch] (unmix* mix ch)) (defn unmix-all "Removes all inputs from the mix." [mix] (unmix-all* mix)) (defn toggle "Atomically sets the state of one or more channels in a mix." [mix state-map] (toggle* mix state-map)) (defn solo-mode "Sets the solo mode of the mix (:mute or :pause)." [mix mode] (solo-mode* mix mode)) ;; --- pub / sub -------------------------------------------------------------- (defprotocol Pub (sub* [p v ch close?]) (unsub* [p v ch]) (unsub-all* [p] [p v])) (defn pub "Creates a pub of ch partitioned by topic-fn. Subscribe with sub." ([ch topic-fn] (pub ch topic-fn (constantly nil))) ([ch topic-fn buf-fn] (let [mults (atom {}) ensure-mult (fn [topic] (or (get @mults topic) (get (swap! mults #(if (% topic) % (assoc % topic (mult (chan (buf-fn topic)))))) topic))) p (reify Mux (muxch* [_] ch) Pub (sub* [_p topic ch close?] (let [m (ensure-mult topic)] (tap m ch close?))) (unsub* [_p topic ch] (when-let [m (get @mults topic)] (untap m ch))) (unsub-all* [_] (reset! mults {})) (unsub-all* [_ topic] (swap! mults dissoc topic)))] (go-loop [] (let [val (! (muxch* m) val) (swap! mults dissoc topic))) (recur))))) p))) (defn sub "Subscribes ch to a topic of pub p." ([p topic ch] (sub p topic ch true)) ([p topic ch close?] (sub* p topic ch close?))) (defn unsub "Unsubscribes ch from a topic of pub p." [p topic ch] (unsub* p topic ch)) (defn unsub-all "Unsubscribes all channels from a pub, or from a topic." ([p] (unsub-all* p)) ([p topic] (unsub-all* p topic))) ;; --- map / merge ------------------------------------------------------------ (defn map "Applies f to the set of first items from each source channel, then second, etc. Closes the output channel when any source closes." ([f chs] (map f chs nil)) ([f chs buf-or-n] (let [chs (vec chs) out (chan buf-or-n) cnt (count chs) rets (atom (vec (repeat cnt nil))) dchan (chan 1) dctr (atom nil) done (mapv (fn [i] (fn [ret] (swap! rets assoc i ret) (when (zero? (swap! dctr dec)) (put! dchan @rets)))) (range cnt))] (if (zero? cnt) (close! out) (go-loop [] (reset! dctr cnt) (dotimes [i cnt] (take! (nth chs i) (nth done i))) (let [rets (! out (apply f rets)) (recur)))))) out))) (defn merge "Returns a channel with all values taken from the source channels chs. Closes after all sources close." ([chs] (merge chs nil)) ([chs buf-or-n] (let [out (chan buf-or-n)] (go-loop [cs (vec chs)] (if (pos? (count cs)) (let [[v c] (alts! cs)] (if (nil? v) (recur (filterv #(not= c %) cs)) (do (>! out v) (recur cs)))) (close! out))) out))) ;; --- deprecated channel ops (rewritten as go-loops) ------------------------- (defn map< "Deprecated - use a transducer. Returns a read-side channel mapping f over ch." [f ch] (let [out (chan)] (go-loop [] (let [v (! out (f v)) (recur))))) out)) (defn map> "Deprecated - use a transducer. Returns a write-side channel mapping f into out." [f out] (let [in (chan)] (go-loop [] (let [v (! out (f v)) (recur))))) in)) (defn filter< "Deprecated - use a transducer." ([p ch] (filter< p ch nil)) ([p ch buf-or-n] (let [out (chan buf-or-n)] (go-loop [] (let [val (! out val)) (recur))))) out))) (defn remove< "Deprecated - use a transducer." ([p ch] (remove< p ch nil)) ([p ch buf-or-n] (filter< (complement p) ch buf-or-n))) (defn filter> "Deprecated - use a transducer." [p out] (let [in (chan)] (go-loop [] (let [v (! out v)) (recur))))) in)) (defn remove> "Deprecated - use a transducer." [p out] (filter> (complement p) out)) (defn- mapcat* [f in out] (go-loop [] (let [val (! out v)) (recur)))))) (defn mapcat< "Deprecated - use a transducer." ([f in] (mapcat< f in nil)) ([f in buf-or-n] (let [out (chan buf-or-n)] (mapcat* f in out) out))) (defn mapcat> "Deprecated - use a transducer." ([f out] (mapcat> f out nil)) ([f out buf-or-n] (let [in (chan buf-or-n)] (mapcat* f in out) in))) (defn unique "Deprecated - use a transducer. Drops consecutive duplicates." ([ch] (unique ch nil)) ([ch buf-or-n] (let [out (chan buf-or-n)] (go (loop [last nil] (let [v (! out v) (recur v)))))) (close! out)) out))) (defn partition "Deprecated - use a transducer. Partitions ch into vectors of n." ([n ch] (partition n ch nil)) ([n ch buf-or-n] (let [out (chan buf-or-n)] (go-loop [arr [] idx 0] (let [v (! out arr) (recur [] 0)))) (do (when (> idx 0) (>! out arr)) (close! out))))) out))) (defn partition-by "Deprecated - use a transducer. Partitions ch by runs of (f v)." ([f ch] (partition-by f ch nil)) ([f ch buf-or-n] (let [out (chan buf-or-n)] (go-loop [lst [] last ::nothing] (let [v (! out lst) (recur [v] new-itm)))) (do (when (> (count lst) 0) (>! out lst)) (close! out))))) out)))