42

我看到许多 Clo​​jure 程序员对新的 core.async 库充满热情,虽然它看起来很有趣,但我很难看出它是如何符合 Clojure 原则的,所以我有以下问题:

  1. 它在任何地方都使用可变状态,正如函数名称通过感叹号所暗示的那样,如 alt!、put!、>! 等。如果您从通道中放置或获取值,则该通道将被就地修改。这不是与 Clojure 偏好不可变数据结构、纯函数等的哲学相悖吗?还是 core.async 仅用于根本无法避免可变事物的情况?
  2. 由于“go”是一个宏(因此修改代码结构)并确保“<!” 直接在 go-block 中使用,不能使用 "<!" 在另一个函数中,像这样:

    (defn take-and-print [c]
     (println (<! c)))
    
    (def ch (chan 1))
    (>!! ch 123)
    
    (go (take-and-print ch))
    
    Assert failed: <! used not in (go ...) block
    

    在我看来,这妨碍了简单性和可组合性。为什么这不是问题?

  3. 也许是前两个问题的结果,很多带有 core.async 的代码都使用了低级结构,例如循环/递归,而不是 map/filter/reduce。这不是倒退一步吗?

我在哪里错过了重点?

提前致谢。

4

6 回答 6

38

第一个问题 - 是的,核心操作是副作用。但是,通道没有通常与可变引用相关的问题,因为它们不代表“位置” - 通道是不透明的,您无法检查它们,实际上您甚至无法查询通道是否已关闭或超出阅读范围零。

第二个问题 - 做任何比浅产量更多的事情都意味着整个程序的转换。这是一种权衡,我认为是合理的。组合的水平是通道而不是块,它们组合得很好。

最后一个问题,您可以轻松地对通道进行 Rx 样式的映射/过滤/减少操作,人们已经这样做了。

于 2013-08-13T00:34:20.613 回答
16

go 宏(它的局部性)的限制也是一个特性:它强制执行有状态操作的源代码局部性。

于 2013-08-14T14:50:06.153 回答
11
  1. 有点相反,Core.async 只能在以不变性为标准的系统中使用。所以 Clojure 的原则启用 core.async 而不是相反。

  2. 这是一个限制,也发生在 clojure 的其他地方,不与%符号组成的匿名函数的限制似乎至少提出了相同的想法。当然,并不是说找到另一种限制情况会使它变得更好。

  3. 我还没有看到我自己,尽管如果你试图采用简单的代码,并且在以一种方式表达时干净,然后以......不是那样的方式表达它,这将是一个倒退。 .

于 2013-08-13T00:34:58.743 回答
5

Rich Hickey 在 blip.tv 的一次讲座中说 Clojure 是“85% 的功能性”。我喜欢将 core.async 视为另外 15% 的一部分。Core.async 非常适合在其他事情中进行可靠的用户交互,这些事情本来可以通过承诺、延迟和其他事情来完成,可能是以一种更混乱的方式。

于 2013-08-13T09:16:01.080 回答
2

每个程序都有两部分,一部分总是非函数谈话数据输入,吐出等等。对于这一部分,我们知道有 core.async,core.async 确实有可变的东西,但请注意两点。通道的状态基本上由库管理。你放在上面的东西是,什么叫做 flowstate。这意味着当您放置类似频道之类的内容时,您不会期望再次访问它,甚至不会更改它。

Core.async 很适合管理程序的这一部分。剩下的,对你的数据进行的所有转换和计算,clojure 会尽力为你提供很好的工具来实现它的功能。

在我看来,这妨碍了简单性和可组合性。为什么这不是问题?

有两个世界,同步世界和异步世界。你可以放东西,或者用 put 把东西从异步世界中取出!并采取!但除此之外(也许还有其他一些功能)这些世界是相互分离的。Clojure 不想成为一种完全异步的语言。功能和数据转换需要可组合。

也许是前两个问题的结果,很多带有 core.async 的代码都使用了低级结构,例如循环/递归,而不是 map/filter/reduce。是不是倒退了一步

可以通过频道进行类似的操作。Core.async 还很年轻,还没有编写所有可能的构造和函数。

但一般来说,如果你有大型数据转换,你真的不想在异步世界中进行它们,你希望将它们放在一个集合中,然后向它抛出类似 reduceres 框架的东西。

主要要理解的是,core.async 不是新标准,它只是帮助您编程的另一件事。有时您需要 STM,有时需要代理,有时需要 CSP,这取决于并且 clojure 想要为您提供所有选项。

人们喜欢 core.async 的一个原因是因为它有助于处理一些原本很难处理的事情,比如处理回调。

于 2014-01-20T21:36:35.093 回答
-1

也许可以使用(<! c)宏及其宏扩展时间来完成使用外部宏的可能解决方案:

这是我的例子:

(ns fourclojure.asynco
      (require [clojure.core.async :as async :refer :all]))

(defmacro runtime--fn [the-fn the-value]
  `(~the-fn ~the-value)
  )
(defmacro call-fn [ the-fn]
  `(runtime--fn ~the-fn (<! my-chan))
  )

(def my-chan (chan))

(defn  read-channel [the-fn]
  (go
  (loop []
    (call-fn the-fn)
    (recur)
    )
  ))

(defn paint []
  (put! my-chan "paint!")
  )

并对其进行测试:

(read-channel print)
(repeatedly 50 paint)

我已经在嵌套中尝试过这个解决方案并且也可以工作。但我不确定它是否是正确的路径

于 2013-10-23T09:23:57.833 回答