在core.async的go块中无法使用for循环?

时间:2014-09-25 14:17:25

标签: clojure core.async

我是clojure core.async库的新手,我试图通过实验来理解它。

但是当我尝试时:

(let [i (async/chan)] (async/go (doall (for [r [1 2 3]] (async/>! i r)))))

它给了我一个非常奇怪的例外:

CompilerException java.lang.IllegalArgumentException: No method in multimethod '-item-to-ssa' for dispatch value: :fn

我尝试了另一个代码:

(let [i (async/chan)] (async/go (doseq [r [1 2 3]] (async/>! i r))))

它根本没有编译器异常。

我完全糊涂了。发生了什么事?

1 个答案:

答案 0 :(得分:18)

因此,Clojure go-block在功能边界处停止转换,原因很多,但最大的是简单性。这在构造惰性seq时最常见:

(go (lazy-seq (<! c)))

获取编译成如下内容:

(go (clojure.lang.LazySeq. (fn [] (<! c))))

现在让我们快速思考一下这应该是什么?假设您可能想要的是包含从c获取的值的延迟seq,但<!需要将函数的剩余代码转换为回调,但是LazySeq期望该函数是同步的。实际上没有办法解决这个限制。

回到你的问题,如果你宏观扩展for你会发现它实际上没有循环,而是扩展成一堆最终调用lazy-seq的代码,所以停车操作不要在体内工作。 doseq(和dotimes)然后由loop / recur支持,因此这些功能完全正常。

还有一些其他地方可能会让你失望with-bindings就是一个例子。基本上,如果宏将您的core.async停放操作粘贴到嵌套函数中,您将收到此错误。

我的建议是尽可能简化你的棋盘体。编写纯函数,然后将go块的主体视为IO的位置。

------------编辑-------------

通过在函数边界处停止转换,我的意思是:go块占用它的主体并将其转换为状态机。对<! >!alts!(以及其他一些)的每次调用都被视为状态机转换,其中块的执行可以暂停。在每个点上,机器变成回调并连接到通道。当此宏达到fn形式时,它将停止翻译。因此,您只能从go块内部调用<!,而不是在代码块内的函数内部调用{{1}}。

这是core.async魔力的一部分。没有go宏,core.async代码看起来很像其他语言中的回调地狱。