不能在core.async的go块中使用for循环?

xud*_*fsd 9 clojure core.async

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

但当我尝试时:

(let [i (async/chan)] (async/go (doall (for [r [1 2 3]] (async/>! i r)))))
Run Code Online (Sandbox Code Playgroud)

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

CompilerException java.lang.IllegalArgumentException: No method in multimethod '-item-to-ssa' for dispatch value: :fn
Run Code Online (Sandbox Code Playgroud)

我尝试了另一个代码:

(let [i (async/chan)] (async/go (doseq [r [1 2 3]] (async/>! i r))))
Run Code Online (Sandbox Code Playgroud)

它根本没有编译器异常.

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

Tim*_*dge 18

所以Clojure go-block在功能边界停止翻译,原因很多,但最大的是简单性.这在构造一个懒惰的seq时最常见:

(go (lazy-seq (<! c)))
Run Code Online (Sandbox Code Playgroud)

获取编译成这样的东西:

(go (clojure.lang.LazySeq. (fn [] (<! c))))
Run Code Online (Sandbox Code Playgroud)

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

所以回到你的问题,如果你宏观扩展for你会看到它实际上没有循环,而是扩展成一堆最终调用的代码,lazy-seq所以停车操作不能在机体内部工作.doseq(并且dotimes)然后由loop/ 支持recur,所以那些将完美地工作.

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

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

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

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

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