purescript-coroutines 中的 `pullFrom` 和 `connect` 有什么区别?

What's the difference between `pullFrom` and `connect` in purescript-coroutines?

我不确定我是否理解 purescript-coroutines 中有 connectpullFrom 函数的原因,以及您何时会同时使用它们。看看这些类型,它们似乎已经习惯了 "direction of communication" 的变化(我不确定这是否是正确的思考方式)。

pullFrom :: forall o m a. MonadRec m => Consumer o m a -> Producer o m a -> Process m a

connect :: forall o f m a. (MonadRec m, Parallel f m) => Producer o m a -> Consumer o m a -> Process m a

所以如果我有一个消费者和一个生产者...

consumer :: forall e a. (Show a) => Consumer a (Eff (console :: CONSOLE | e)) Unit
consumer = forever do
  s <- await
  lift (log $ show s)

numberProducer :: forall m. (Monad m) => Producer Int m Unit
numberProducer = go 0
  where
  go i = do emit i
            go (i + 1)

我认为消费者可以从生产者那里获取信息,如果我 运行 这样,我可以看到显示的数字...

main = do
  runProcess (pullFrom consumer numberProducer)

但是如果我connect生产者给消费者,好像什么也做不了。我假设当您将生产者连接到消费者时,信号的方向与 pullFrom 相反,但我不确定这个想法是否正确。

main = do
  runProcess (connect producer consumer)

好吧,这其中有一个有趣的小惊喜......不过我会在一分钟内谈到这一点。

pullFrom 被引入,以便消费者在进程形成时 "in charge" - 只要消费者打开(等待输入),进程就存在。

connect运行只要生产者或消费者中的任何一个是打开的,并且只有在它们都完成时该过程才会终止。

为了实现这一点,connect 有一个 Parallel class 约束,因为创建的过程取决于消费者和生产者 - pullFrom 不需要这个,因为该过程仅依赖于 consumer.

这就是 "fun" 惊喜的来源 - 让我困惑了一分钟。 Eff 不是 Parallel...那么您的代码如何工作?这是因为它正在为 main:

推断这种类型
main :: forall t. (Parallel t (Eff (console :: CONSOLE))) => Eff (console :: CONSOLE) Unit

所以当你的程序是 运行 时什么也不会发生,因为在 JS 中,main 期望为 Parallel 约束传递一个字典,然后是 Eff 待评价。生成的对 main 的调用只是 Main.main();,因此它实际上从未评估 Eff,因为它需要 Main.main(impossibleParallelDictionary)();.

尝试将此类型添加到您的 main:

main :: Eff (console :: CONSOLE) Unit

你会发现它不再进行类型检查。

尽管如此,您可以使用 Aff,并且使用 Aff 时,connectpullFrom 之间的区别对于此示例是无法区分的:

import Prelude

import Control.Coroutine (Consumer, Producer, await, connect, emit, runProcess)
import Control.Monad.Aff (Aff, launchAff)
import Control.Monad.Aff.Console (CONSOLE, log)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Exception (EXCEPTION)
import Control.Monad.Rec.Class (forever)
import Control.Monad.Trans.Class (lift)

consumer :: forall e a. (Show a) => Consumer a (Aff (console :: CONSOLE | e)) Unit
consumer = forever do
  s <- await
  lift (log $ show s)

numberProducer :: forall m. (Monad m) => Producer Int m Unit
numberProducer = go 0
  where
  go i = do emit i
            go (i + 1)

main :: Eff (err :: EXCEPTION, console :: CONSOLE) Unit
main = void $ launchAff $ runProcess (connect numberProducer consumer)

如果我们稍微修改示例,我们可以看到差异的图示:

import Prelude

import Control.Coroutine (Consumer, Producer, await, emit, connect, runProcess)
import Control.Monad.Aff (Aff, launchAff, later')
import Control.Monad.Aff.Console (CONSOLE, log)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Exception (EXCEPTION)
import Control.Monad.Trans.Class (lift)

consumer :: forall e a. (Show a) => Consumer a (Aff (console :: CONSOLE | e)) Unit
consumer = do
  s <- await
  lift (log $ show s)

numberProducer :: forall eff. Producer Int (Aff eff) Unit
numberProducer = do
  emit 0
  lift $ later' 10000 $ pure unit

main :: Eff (err :: EXCEPTION, console :: CONSOLE) Unit
main = void $ launchAff $ runProcess (connect numberProducer consumer)

这样,程序将打印 0,等待 10 秒,然后退出。如果为 consumer `pullFrom` numberProducer 切换 connect numberProducer consumer 程序将打印 0 并立即退出。