如何在单子变换器中进行分支(fork)

10

考虑一个单子变换器堆栈,比如

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
...
newtype J = J { runJ :: ErrorT Foo (StateT Bar IO) a } deriving (Applicative, Functor, etc)

还有一些 J 中的函数:

peekNextQuux :: J Quux
peekNextQuux = ...

withJ :: J a -> IO (Either Foo a)
withJ = ...

然后我发现自己在J上下文中。我可以编写:

f = withJ $ peekNextQuux >>= liftIO . print

现在我想在J上下文中的单独线程中查看和打印quuxes

g = withJ . liftIO . forkIO . forever $ peekNextQuux >>= liftIO . print

显然这样不会起作用。我猜应该有一种方法可以解决这么简单的问题,只是想不出来。

2个回答

9
你希望它如何工作?分离的线程需要访问一些状态和错误处理,因为J包装了StateTErrorT。这个线程应该如何获得这些访问权限?当新线程更新状态时,旧线程中的状态是否也应该被更改?当新线程抛出异常时,旧线程是否应该停止?
这是不可能实现的,因为StateTErrorT是纯的单子变换器,所以我描述的行为是不可能实现的。你必须显式地将状态传递到新线程,并在那里运行一个新的状态单子才能使其工作:
g = withJ . ... $ do
  state <- get
  liftIO . forkIO $ do
    flip execStateT state . forever $ peekNextQuux >>= liftIO . print

9

我不确定这是否是你需要的,但听起来你正在寻找一个函数。

forkJ :: J () -> J ThreadId

这段文本涉及到IT技术,其中提到了一些Haskell语言相关的概念。与forkIO类似的是forkJ,但在J上下文中工作。总体来说,dflemstr提出的所有观点都是正确的。由于Haskell的纯洁性,关于状态管理还存在许多未解决的问题。

然而,如果您愿意稍微重构您的逻辑,可能有一个选项适合您(如果您所需的仅是具有对发出fork时使用的原始状态的访问权限的单独线程),那就是依赖于monad-control的lifted-base包。只要您的变换器栈底部有IO,它就会为您提供上面的forkJ函数。

现在,如果您希望两个线程以有状态的方式进行通信,使子级引发的错误作为ErrorT机制的一部分传播到主线程中,则这是不可能的(如dflemstr所解释的)。但是,您可以使用来自Control.Concurrent模块系列的结构在两个线程之间建立通信通道。以下某个模块可能具有您所需的功能:

Control.Concurrent.Chan
Control.Concurrent.MVar
Control.Concurrent.STM

好的,我会看一下 lifted-base。谢谢。 - Matvey Aksenov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接