好吧,traverse 只需要一个Applicative。您可以通过使用替代Applicative for IO 使mapM 并行执行其操作(这就是mapConcurrently 的实现方式)。但是,这个Applicative 没有合法的Monad 实例:(>>=) 和其他Monad 操作将与(<*>) 和其他Applicative 操作不一致。例如。 mf >>= \f -> mx >>= \x -> return (f x) 将不等同于 mf <*> mx,因为 (>>=) 不能并行执行其参数,但 (<*>) 会。 (您可能可以使用unsafeInterleaveIO 创建一个工作的Monad 实例,但是,好吧,unsafeInterleaveIO。)
您可以做的一件事是传递Tasks 和Applicative 函子,与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 包装在exec 中。 AFAICT、runSimple . taskSimple = id 和 taskSimple . runSimple 是幂等的。