【问题标题】:How to combine state and continuation monads in F#如何在 F# 中组合状态和延续单子
【发布时间】:2012-06-29 08:09:09
【问题描述】:

我正在尝试使用任务并行库对树求和,其中子任务仅在树被遍历到一定深度之前生成,否则它使用连续传递样式对剩余的子节点求和,以避免堆栈溢出。

但是,代码看起来很丑 - 使用 state monad 来携带当前深度会很好,但 state monad 不是尾递归的。或者,我将如何修改延续单子以携带状态?或者创建状态和延续单子的组合?

let sumTreeParallelDepthCont tree cont = 
  let rec sumRec tree depth cont =
    let newDepth = depth - 1
    match tree with
    | Leaf(num) -> cont num
    | Branch(left, right) ->
      if depth <= 0 then
        sumTreeContMonad left (fun leftM ->
          sumTreeContMonad right (fun rightM ->
            cont (leftM + rightM )))
      else 
        let leftTask = Task.Factory.StartNew(fun () -> 
              let leftResult = ref 0
              sumRec left newDepth (fun leftM -> 
                leftResult := leftM)
              !leftResult
              )
        let rightTask = Task.Factory.StartNew(fun () -> 
              let rightResult = ref 0
              sumRec right newDepth (fun rightM ->
                rightResult := rightM)
              !rightResult
              )
        cont (leftTask.Result + rightTask.Result)
  sumRec tree 4 cont // 4 levels deep

我在这篇博文中有更多详细信息:http://taumuon-jabuka.blogspot.co.uk/2012/06/more-playing-with-monads.html

【问题讨论】:

  • 这是一个奇怪的组合。您正在并行化以提高性能,但使用效率非常低的延续传递样式。

标签: f# state monads monad-transformers continuation


【解决方案1】:

我认为首先了解您的要求很重要。

  • 算法的顺序版本不需要保留depth(因为它总是处理树的其余部分)。但是,它需要使用延续,因为树可能很大。

  • 另一方面,并​​行版本需要保留depth(因为您只想进行有限数量的递归调用),但不需要使用延续(因为深度相当有限,当你开始一个新任务时,它无论如何都不会保留堆栈)。

这意味着您根本不需要将这两个方面结合起来。然后你可以用一种非常直接的方式重写并行版本:

let sumTreeParallelDepthCont tree =  
  let rec sumRec tree depth = 
    match tree with 
    | Leaf(num) -> num 
    | tree when depth <= 0 -> 
        sumTreeContMonad tree id
    | Branch(left, right) ->
        let leftTask = Task.Factory.StartNew(fun () -> sumRec left (depth + 1))
        let rightResult = sumRec right (depth + 1)
        leftTask.Result + rightResult
  sumRec tree 4 // 4 levels deep 

无需从sumTreeContMonad 复制代码,因为您可以在tree when depth &lt;= 0 的情况下在当前树上调用它。

这也通过创建Task&lt;int&gt; 而不是Task 来避免使用参考单元格,并且我修改了算法以仅生成一个后台任务并在当前线程上执行第二部分工作。

【讨论】:

  • 这比我的回答要好:)
  • 我认为关键是简单。当没有它们的情况下有足够有效的解决方案时,您最终可能会跳下兔子洞来寻找单子实现。
【解决方案2】:

在我看来,深度看起来不错,但难看的是参考单元格和分配。我不清楚你为什么需要它们;我认为只是将id(身份函数)作为cont 参数传递意味着sumRec 将返回该值,然后您就不需要参考单元格了。 (我可能错了,这是一目了然的分析。)

(作为风格问题,我也将摆脱 newDepth 并在递归调用站点内联 (depth-1)。)

最后,我不知道sumTreeContMonad 是什么,但看起来你可以只使用sumRec t -1 k 而不是sumTreeContMonad t k,它的工作原理是一样的。

(如果您的博客有代码,而不是代码图片,我可能会发布我自己的经过这些改进的代码,但我不想转录数据类型等。为什么要发布图片?)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-06
    • 2016-02-02
    • 2011-10-30
    • 2010-10-14
    • 2014-10-21
    • 2017-01-30
    • 2011-08-22
    • 2015-08-10
    相关资源
    最近更新 更多