好吧,traverse
只需要一个Applicative
. 您可以mapM
通过使用替代Applicative
for来并行执行其操作IO
(这就是mapConcurrently
实现方式)。但是,这Applicative
并没有合法的Monad
例子:(>>=)
和其他Monad
操作不会(<*>)
和其他Applicative
操作一致。例如,mf >>= \f -> mx >>= \x -> return (f x)
将不等同于mf <*> mx
,因为(>>=)
不能并行执行其参数,但(<*>)
会。(您可能可以Monad
使用创建一个工作实例unsafeInterleaveIO
,但是,好吧,unsafeInterleaveIO
。)
您可以做的一件事是传递Task
s 一个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
代码根本无法从单独的Monad
和Applicative
上下文中受益,您可以使用这个智能构造函数
taskSimple ∷ (∀f. Monad f ⇒ (k → f v) → f v) → Task k v
taskSimple r = Task (\exec lookup -> r (exec . lookup))
以避免包装每个lookup
in exec
。AFAICT,runSimple . taskSimple = id
和taskSimple . runSimple
是幂等的。