【问题标题】:Haskell: parallel computation and the 'sequential property' of monadsHaskell:并行计算和单子的“顺序属性”
【发布时间】:2020-01-03 23:17:31
【问题描述】:

我对为什么 REPA 函数 computeP 将其结果打包到一个 monad 感到困惑。它具有以下类型签名。

computeP :: (Load r1 sh e, Target r2 e, Source r2 e, Monad m) =>
            Array r1 sh e -> m (Array r2 sh e)

this tutorial 它说

这样做的原因是 monad 给出了明确定义的序列概念,因此 computeP 强制在 monad 计算的特定点完成并行计算。

同样,Stack Overflow 上的 this answer 声明

Repa 中的并行计算必须是 monadic 的原因部分与惰性有关,但主要与 Repa 无法处理嵌套并行性有关。 Monad 的顺序属性在很大程度上解决了它[.]

问题

  • 拥有这种“顺序属性”究竟是什么意思?
  • monad 如何执行此操作?
  • 对于computeP 的例子:没有限制使用哪个monad,所以我可以使用identity monad。那么使用以下函数来解包 monad 是否可以,还是会因为缺少这种顺序属性而产生意想不到的结果?如果可以的话,是否还需要使用 monad?

    import Data.Functor.Identity
    import Data.Array.Repa.Eval
    import Data.Array.Repa
    
    myComputeP :: (Load r1 sh e, Target r2 e, Source r2 e) => Array r1 sh e -> Array r2 sh e
    myComputeP = runIdentity . computeP
    

任何帮助都会很棒。

【问题讨论】:

  • 你应该一次只关注一个问题。
  • @MichaelLitchard 我猜你的意思是What does having this 'sequential property' meanwhy does computeP pack its result in a monad 这两个问题。后者实际上是作为前者的一个例子:我猜computeP因为这个顺序属性而使用monad,但我想澄清一下这个顺序属性是如何工作的。你是这个意思吗?如果您能澄清,我非常愿意澄清问题或将其拆分为多个问题。
  • 真的是“这对任何单子都有效吗”/“单子令人困惑 hellppp”似乎偏离了主题,至少对我而言。但我也认为这是一个不错的问题,三个子问题很好地传达了您想要理解的内容。
  • @luqui 好的,我明白你的意思。世界不需要另一个“单子令人困惑的 hellppp”问题,我也不打算让它看起来那样。我想我希望找到一个答案,以我自己的直觉为起点来解释这个连续的事情,但它可能会偏离真正的问题。我将删除问题的最后一部分。
  • @Safron 仅供参考,massiv 在精神上与 Repa 相似,但不受您问题的限制,因此嵌套并行性完全可以。

标签: haskell monads repa


【解决方案1】:

这个 monad 约束是一种启发式技巧。它可以帮助有纪律的用户避免嵌套并行,但对恶意或无知的用户无能为力。

嵌套并行是这样一种情况,即在并行计算某个数组时,您最终不得不并行计算另一个数组。 Repa 不支持(原因不重要),所以尽量避免。

computeP 的类型有助于确保并行计算彼此按顺序进行,但它远非密封;它只是一个“尽力而为”的抽象。

monad 如何执行此操作?

实际上,computeP 只适用于绑定 (>>=) 在其第一个参数中是严格的 monad,因此在 u >>= k 中,函数 k 将仅在评估 u 后应用.那么如果你使用computeP 和这样的单子,

do w <- computeP v
   k w

保证向量w在传递给k之前被评估,这可以安全地执行其他computeP操作。

  • 严格单子示例:IO、严格StateMaybe[]
  • 惰性单子示例:Identity、惰性StateReader。 (惰性单子可以变得严格,但反过来不行。特别是,如果您只想进行 Repa 计算,则可以定义严格的恒等单子。)

为了防止嵌套并行,computeP 的类型有意使其在可能并行完成的操作中使用起来很麻烦,例如 map :: (a -&gt; b) -&gt; Array _ _ a -&gt; Array _ _ bfromFunction :: sh -&gt; (sh -&gt; a) -&gt; Array _ _ a,它们采用非单子函数。你仍然可以明确地打开computeP,例如使用runIdentity,正如你所注意到的:如果你愿意,你可以向自己的脚开枪,但你需要为枪上膛、指向下方并扣动扳机。


希望这能回答 Repa 发生的事情。以下是回答另一个问题的理论题外话:

拥有这个“顺序属性”到底是什么意思?

那些引文很随意。在我看来,“顺序性”和“单子”之间的关系是双重的。

首先,对于 Haskell 中的许多 monad,(&gt;&gt;=) 的定义自然地规定了求值顺序,通常是因为它立即与第一个参数进行模式匹配。如前所述,这就是 Repa 依赖于强制 computeP 计算按顺序发生的原因(这就是为什么如果你将它专门化为 Identity 它会中断;它不是一个严格的单子)。总的来说,这是惰性求值的一个相当小的细节,而不是一般的 monad 的任何东西。

其次,纯函数式编程的一个核心思想是一流的计算,具有对效果和组合的明确定义。在这种情况下,效果是向量的并行评估,而我们关心的组合是顺序组合。 Monad 为顺序组合提供了一个通用模型或接口。这也是 monad 有助于解决 Repa 中避免嵌套并行问题的另一部分原因。

重点不是 monad 具有固有的顺序方面,而是顺序组合本质上是 monadic:如果您尝试列出您期望从任何值得称为“顺序组合”的事物中获得的一般属性,那么您就是最终可能会得到一起称为“monad”的属性;这是 Moggi 的开创性论文“计算和单子的概念”的要点之一。

“Monads”不是一个神奇的概念,而是非常普遍的,所以很多东西恰好是 monads。毕竟,主要要求是有一个关联操作;这是关于顺序组合的一个非常合理的假设。 (如果,当您听到“关联性”时,您想到的是“monoid”或“category”,请注意所有这些概念都统一在“monoid objects”的保护伞下,因此就“associativity”而言,它们是都是一样的想法,只是属于不同的类别。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-13
    • 1970-01-01
    • 2018-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多