我目前正在玩 Polysemy,重写我的一个小玩具项目以适应它。我偶然发现了一段使用 的代码pooledMapConcurrentlyN
,因此基本上是具有有限并发的并行版本。
我可以将我的示例简化为:
foo :: Sem r Int
foo = do
res <- pooledMapConcurrentlyN 3 action (["foo", "bar", "baz"] :: [String])
pure $ sum res
action :: String -> Sem r Int
action = pure. length
这不会编译,因为没有MonadUnliftIO (Sem r)
. 它在我使用时会编译traverse
,但我正在寻找一个并发版本。我不确定我现在应该走哪条路。
我看到以下选项:
- 实现一个
MonadUnliftIO (Sem r)
实例。我看到在这个 GitHub issue中有一些关于添加/实现这样一个实例的讨论。但是,我不清楚这样做是否是个好主意。 - 使用除此之外的东西
pooledMapConcurrentlyN
会给我一个等效的行为。我知道有parTraverse
来自par-dual包,但这需要一个ParDual
实例。该parallel
软件包也可以使解决方案成为可能,但是我对此并不熟悉,因此无法确定是否可行。 - 将平行遍历建模为效果。我试过了,但我无法实现效果。我试过的效果定义是这样的:
data ParTraverse m a where
TraverseP :: (Traversable t) => Int -> (a -> m b) -> t a -> ParTraverse m (t b)
我对 GADT 和 Polysemy 都不是很熟悉,所以我可能在这里遗漏了一些明显的东西。
编辑:正如下面的答案所指出的,最合适的解决方案是将其建模为效果并在效果解释中处理并发,而不是在业务逻辑中处理。这意味着我正在寻找类似于上述ParTraverse
效果的更高阶效果(?):
data ParTraverse m a where
TraverseP :: (Traversable t) => (a -> m b) -> t a -> ParTraverse m (t b)
makeSem ''ParTraverse
parTraverseToIO :: (Member (Embed IO) r) => Sem (ParTraverse ': r) a -> Sem r a
parTraverseToIO = interpretH $ \case
TraverseP f ta -> do
_something
我不确定这种类型签名是否正确(动作应该有 typea -> Sem r b
吗?for 的签名traverse
有一个Applicative
约束m
,我将如何建模?)