在個人上一篇文章中,用兩種不一樣的方法實現了 transformer 函數到 actor。其中 pipe
版本明顯更加簡單。這引起了個人進一步思考。程序員
顯然,actor 自己實現中用函數來進行循環與 transducer 的思想高度一致。實際上,二者都經過封裝狀態來實現了純函數化的外在表現。因爲 transducer/transformer 在 clojure 中已經被實現爲通用的模式,咱們是否還有必要來使用 actor 呢?segmentfault
在 clojure 的一個 actor 庫實現,pulsar 的文檔中, 做者闡述了在程序員用戶角度來看的 actor 與 channel 的區別:數據結構
channel 更像是水管,裏面流動的是相同形式的數據,將這些數據送往程序不一樣部分;而 actor 更像是接線板,它支持各類不一樣的鏈接方式(消息)。異步
從這個定義來看,actor 實在是一種面向對象的觀念,它能夠直接被視爲是異步對象。這是爲何在 erlang 或 pulsar 中,actor 從實現上就與模式匹配(pattern matching)牢牢鏈接在了一塊兒。咱們甚至能夠將每種不一樣的消息看作是對象的不一樣方法(Java 中的method)。async
但咱們真的須要這麼返回到面向對象的領域嗎?將咱們的函數從新組織成方法是不是合理的思路?函數
仔細看看 core.async 中 pipe
, pub
等函數的實現,我發現通道自己就是一個能夠容納主動處理過程的數據結構,咱們沒有顯著的必要再引入新概念來完成它!它自己包含輸入(ReadPort)和輸出(WritePort)兩端,多麼象 Unix 文件啊。惟一的麻煩是,core.async 自己沒有提供函數來方便地將通道進行組合。所以,下面幾個函數:ui
(require '[clojure.core.async :as a]) (defn attach "將 ch-input 和 ch-output 兩個通道鏈接成爲一個新的通道: 寫這個通道將放進 ch-input, ch-input 則會主動將值寫進 ch-output, 讀取這個通道將得到 ch-output 的值." [ch-input ch-output] (reify p/ReadPort (take! [_ f] (p/take! ch-output f)) p/WritePort (put! [_ v f] (p/put! ch-input v f)) p/Channel (close! [_] (p/close! ch-input)) (closed? [_] (p/closed? ch-input)))) (def xf-chan "使用 transform 函數建立一個主動通道, 寫入它的數據會被 xf 轉換, 能夠在從它讀出" (partial a/chan (a/dropping-buffer 32))) (defn | [& chs] (reduce (fn [rst cur] (a/pipe rst cur) (attach rst cur)) chs)) (defn ac-prn "一個簡單的打印過渡主動通道, 通常用於開發調試" [& [name]] (xf-chan (map (fn [x] (-> (or name "output") (str ":" x) println) x))))
能夠自由地將 channel 進行拼裝組合。例如:調試
(def ac1 (| (a/to-chan [1 2 3]) (xf-chan (map inc)) (ac-prn))) ;會直接打印出 2, 3, 4 (a/<!! ac1) ; 2
鏈接函數|
很好地模仿了 Unix 組合的含義,但它還要更好:組合完成後的管道仍然是一個通道,咱們能夠很容易地從新組合它。code