actor 模式与 transducer 的关系——进一步思考

前言

在我的上一篇文章中,用两种不同的方法实现了 transformer 函数到 actor。其中 pipe 版本明显更加简单。这引发了我的进一步思考。

显然,actor 本身实现中用函数来进行循环与 transducer 的思想高度一致。实际上,两者都通过封装状态来实现了纯函数化的外在表现。由于 transducer/transformer 在 clojure 中已经被实现为通用的模式,我们是否还有必要来使用 actor 呢?

actor 与 channel 的区别

在 clojure 的一个 actor 库实现,pulsar 的文档中, 作者阐述了在程序员用户角度来看的 actor 与 channel 的区别:

channel 更像是水管,里面流动的是相同形式的数据,将这些数据送往程序不同部分;而 actor 更像是接线板,它支持各种不同的连接方式(消息)。

从这个定义来看,actor 实在是一种面向对象的观念,它可以直接被视为是异步对象。这是为什么在 erlang 或 pulsar 中,actor 从实现上就与模式匹配(pattern matching)紧紧连接在了一起。我们甚至可以将每种不同的消息看做是对象的不同方法(Java 中的method)。

但我们真的需要这么返回到面向对象的领域吗?将我们的函数重新组织成方法是否是合理的思路?

主动通道

仔细看看 core.async 中 pipe, pub 等函数的实现,我发现通道本身就是一个可以容纳主动处理过程的数据结构,我们没有显著的必要再引入新概念来完成它!它本身包含输入(ReadPort)和输出(WritePort)两端,多么象 Unix 文件啊。唯一的麻烦是,core.async 本身没有提供函数来方便地将通道进行组合。因此,下面几个函数:

(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/
连接函数| 很好地模仿了 Unix 组合的含义,但它还要更好:组合完成后的管道仍然是一个通道,我们可以很容易地重新组合它。

关键字:clojure


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部