我是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))))
它根本没有编译器异常。
我完全糊涂了。发生了什么事?
答案 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代码看起来很像其他语言中的回调地狱。