actor 模式與 transducer 的關係——進一步思考

前言

在個人上一篇文章中,用兩種不一樣的方法實現了 transformer 函數到 actor。其中 pipe 版本明顯更加簡單。這引起了個人進一步思考。程序員

顯然,actor 自己實現中用函數來進行循環與 transducer 的思想高度一致。實際上,二者都經過封裝狀態來實現了純函數化的外在表現。因爲 transducer/transformer 在 clojure 中已經被實現爲通用的模式,咱們是否還有必要來使用 actor 呢?segmentfault

actor 與 channel 的區別

在 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

相關文章
相關標籤/搜索