1

假设我有一个任务,它代表一些计算,从k那里v必须从外部获取一些输入。

newtype Task k v = Task { run ∷ ∀ f. Monad f ⇒ (k → f v) → f v }

对于某些任务mapM将被使用,例如获取多个密钥。我想专门mapM研究一些单子。专门针对IO我想用来Control.Concurrent.Async.mapConcurrently同时执行 IO 操作的 monad。

我的第一直觉是引入一个包装器类型

newtype AsyncIO a = AsyncIO { io :: IO a }

然后介绍

instance Monad AsyncIO

但是这不起作用,因为在当前的 GHC 实现mapM中是用 定义的traverse,即在Traversable.

有没有一个优雅的解决方案?

4

2 回答 2

5

好吧,traverse只需要一个Applicative. 您可以mapM通过使用替代Applicativefor来并行执行其操作IO(这就是mapConcurrently实现方式)。但是,这Applicative并没有合法的Monad例子:(>>=)和其他Monad操作不会(<*>)和其他Applicative操作一致。例如,mf >>= \f -> mx >>= \x -> return (f x)将不等同于mf <*> mx,因为(>>=)不能并行执行其参数,但(<*>)会。(您可能可以Monad使用创建一个工作实例unsafeInterleaveIO,但是,好吧,unsafeInterleaveIO。)

您可以做的一件事是传递Tasks 一个Applicative函子,与 s 分开Monad,然后提供一个自然转换以将前者中的每个计算注入到后者中。查找函数也应该在Applicative上下文中。

newtype Task k v = Task { run ∷ ∀f f'. (Monad f, Applicative f')
                              ⇒ (∀a. f' a → f a)
                              → (k → f' v) → f v
                        }

如果您没有任何特殊内容Applicative,只需id用作自然转换:

runSimple ∷ Monad f ⇒ Task k v → (k → f v) → f v
runSimple t = run t id

而且IO,特殊Applicative函子已经为您很好地打包在Control.Concurrent.Async.Concurrently

runWithParallelIO ∷ Task k v → (k → IO v) → IO v
runWithParallelIO t lookup = run t runConcurrently (Concurrently . lookup)

你会这样写Task

task ∷ Task _k _v
task = Task go
  where go exec lookup = do _
                            xs <- exec $ mapM lookup _
                            _

如果您发现自己编写的Task代码根本无法从单独的MonadApplicative上下文中受益,您可以使用这个智能构造函数

taskSimple ∷ (∀f. Monad f ⇒ (k → f v) → f v) → Task k v
taskSimple r = Task (\exec lookup -> r (exec . lookup))

以避免包装每个lookupin exec。AFAICT,runSimple . taskSimple = idtaskSimple . runSimple是幂等的。

于 2018-07-30T04:11:10.680 回答
0

已运行 takemapMmapConcurrently作为附加参数,或者可能较少作为隐式参数传递,但我不确定后者是否会与 f 的量化发生冲突。

于 2018-07-30T00:53:15.450 回答