【问题标题】:Can I represent non-sequential/parallel execution with Monads?我可以用 Monads 表示非顺序/并行执行吗?
【发布时间】:2017-10-17 12:09:57
【问题描述】:

所以我终于明白了为什么 Applicatives 对表示并行执行非常有用,而 Monads 对于表示顺序执行非常有用。

话虽如此,我也明白 Monads 比 Applicatives 更强大,那么我可以用 bind 函数来表示 ap 函数吗?

换句话说...我可以用 Monads 表示并行执行吗?

【问题讨论】:

  • 阅读this answer,了解“更强大”的含义和含义。
  • "我可以用绑定函数来表示 ap 函数吗?" - 可以。每个 monad 都是一个应用程序(你可能需要 of + bind)。 “我可以用 Monads 表示并行执行吗?” 也许,但不太可能。并非每个应用程序都是 monad。 “换句话说...” - 不,这是两个非常不同的问题。
  • "每个 monad 都是一个应用程序" & "应用程序对于表示并行执行很有用" = _"我可以用 Monads 表示执行"。 ..对吗?
  • 没有。您对并行执行的表示(不幸的是您没有发布)可能符合应用法则,但单子法则可能不适用。如果它一开始就不是 monad,那么每个 monad 都是应用程序也无济于事。
  • 我开始认为您只是在寻找与 ap 类似类型的东西,它做完全不同的事情:-) 是的,并行性通常是明确的,除非它纯粹被视为没有经过推理的实现优化。

标签: scala haskell monads applicative


【解决方案1】:

The Monad laws对此有话要说:

此外,Monad 和 Applicative 操作应该关联为 如下:

pure = return
(<*>) = ap

鉴于apdefined 以按顺序组合计算,

ap mf mx = do
    f <- mf
    x <- mx
    return (f x)

只有一种方法可以理解该定律:公开单子接口的类型不能使用Applicative 进行并行计算。你可以为你的 monad 提供一个 newtype 包装器,它有一个并行的 Applicative 实例和 no Monad 实例,但你不能同时做这两个。


在理论上,理论和实践是相同的,但在实践中却不是。事实上,在现实世界中,您确实看到人们违反这些规则并将上述法律解释为意味着(&lt;*&gt;) 应该在道德上等同于ap,即使它不是完全 em> 等价的。

我所知道的最好的例子恰好是直接解决您的问题的例子。 Haxl 是一个为并发 IO 实现特定领域语言的库。 GenHaxl monad 的 &lt;*&gt; 在可能的情况下自动并行化两个计算,而它的 &gt;&gt;= 按顺序运行它们(因为它必须这样做)。这显然违反了法律条文,但由于 Haxl 旨在用于数据库读取,它不会改变任何东西(而不是 写入,它会)你可以摆脱它,世界不会爆炸。

【讨论】:

  • 等等! ap 和 不同?
  • 通常不会。在 Haxl 的情况下,是的,但他们违反了规则。
  • 我的理解是,法律上的平等通常被解释为外延平等。因此,如果&lt;*&gt; 总是返回与ap 相同的结果,那么它的实现方式是否不同并不重要。特别是如果它能够使用并行执行并且总是得到与顺序执行相同的结果(这对所有单子来说都是不可能的),那么这是允许的。这不正确吗?
  • @Ben &lt;*&gt; 是一个纯函数,它的返回值是一个计算 m a。并行运行的计算不同于顺序运行的计算,即使它返回的 a 最终是相同的。如果您可以捕获两个行为不同的计算,例如通过使用数据库分析器查看并行发生的查询,那么它们必须是不同的计算! forkIO foofoo 是不同的计算方法,您应该怀疑任何要求您将它们视为相同的代数定律公式!
  • 也就是说,我同意您的观点确实适用于例如parSeq,它采用纯计算并将其并行化。没有办法观察使用 parSeq 编写的计算与没有编写的计算不同。 (它可能会更快地计算一个值,但它总是返回相同的值。)
【解决方案2】:

您可以从FunctorMonad 实现&lt;*&gt;,因此也可以实现ap

class Functor m => Monad m where
  join :: m (m a) -> m a
  return :: a -> m a

(>>=) :: Monad m => m a -> (a -> m b) -> m b
m >>= f = join $ fmap f m

(<*>) :: Monad m => m (a -> b) -> m a -> m b
fs <*> xs = fs >>= \f -> xs >>= \x -> return (f x)

ap :: Monad m => m (a -> b) -> m a -> m b
ap = (<*>)

这个例子隐藏了Haskell的标准Monad定义,而是用joinreturn来定义Monad,但是你也可以从(&gt;&gt;=)定义join;两种方式都可以。

【讨论】:

  • 真的会并行执行吗?
  • @caeus 这取决于类型,但正如 Bergi 所写,虽然所有 monad 都是 applicative,但并非所有 applicative 都是 monad。
  • The Monad laws&lt;*&gt; 应该与ap 相同,因此技术上 具有单子接口的类型不应覆盖&lt;*&gt; 来进行并行处理计算。在实践中,您会看到人们通过定义&lt;*&gt; 来改变规则(例如Haxl),它在道德上 等同于ap,即使它实际上可能表现稍有不同。 (在 Haxl 的情况下,&lt;*&gt; 并行运行计算,而 ap 顺序运行它们。)
  • 我决定把它写成answer
【解决方案3】:

考虑一个案例:假设我们有两个未来。

val future1 = Future {
  //some long running computation
  1
}

val future2 = Future {
 // some othe long running computation
 2
}

future1.flatMap(_ => future2)

在上述情况下,future1future2 并行运行,前提是执行池中有足够的线程。

我们能够并行运行期货。那么,这是什么意思呢?

当先前任务和当前任务(monad)之间存在数据依赖关系时,就会出现 Monad。但是,如果它们可以并行运行时没有数据依赖性(至少在期货的情况下)。

现在考虑一个案例:

val future1 = Future {
 // long running task
 1
}

def compute(value: Int) = Future { value + 1}

future1.flatMap(value => compute(value))

现在一个未来在其他未来完成之后运行。由于数据依赖性,现在执行必须是串行的。第二个未来取决于第一个未来的价值。

【讨论】:

  • 确实,如果我使用 Monix 任务或猫 IO monad 怎么办? (哪个比期货更具参考透明性)?
  • 我主要依赖于实现。
  • 但是如果没有数据依赖,如果实现关心速度和性能,那么即使在 monads 的情况下,并行也是有意义的
猜你喜欢
  • 2014-09-23
  • 1970-01-01
  • 2011-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-07
  • 2023-02-02
  • 2013-11-05
相关资源
最近更新 更多