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

相关文章
相关标签/搜索