在个人上一篇文章中,用两种不一样的方法实现了 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