【问题标题】:What is wrong with 100000 factorial using ContinuationMonad?使用 Continuation Monad 的 100000 阶乘有什么问题?
【发布时间】:2013-10-03 05:30:15
【问题描述】:

递归是一种强大的技术,因为它具有强大的可描述性。尾递归提供比普通递归更强大的计算,因为它将递归变为迭代。 Continuation-Passing Style (CPS) 可以将大量循环代码更改为尾递归。 Continuation Monad 提供递归语法,但本质上它是尾递归,也就是迭代。 100000阶乘应该合理使用Continuation Monad。这是代码。

type ContinuationBuilder() =
   member b.Bind(x, f) = fun k -> x (fun x -> f x k)
   member b.Return x = fun k -> k x
   member b.ReturnFrom x = x
(*
type ContinuationBuilder =
  class
    new : unit -> ContinuationBuilder
    member Bind : x:(('d -> 'e) -> 'f) * f:('d -> 'g -> 'e) -> ('g -> 'f)
    member Return : x:'b -> (('b -> 'c) -> 'c)
    member ReturnFrom : x:'a -> 'a
  end
*)
let cont = ContinuationBuilder()
//val cont : ContinuationBuilder
let fac n =
    let rec loop n =
      cont {
              match n with
              | n when n = 0I -> return 1I   
              | _ -> let! x = fun f -> f n
                     let! y = loop (n - 1I)   
                     return x * y
           }
    loop n (fun x -> x)

let x2 = fac 100000I

有错误消息:“由于 StackOverflowException 而终止进程。”

使用 ContinuationMonad 的 100000 阶乘有什么问题?

【问题讨论】:

  • “尾递归提供比普通递归更强大的计算,因为它将递归变为迭代。”这并不完全正确。如果递归太复杂而无法变成循环,则它只是将当前堆栈帧重新用于函数的下一次调用。

标签: f# monads tail-recursion continuations


【解决方案1】:

您可能需要将此成员添加到您的 monad 构建器:

member this.Delay(mk) = fun c -> mk () c

【讨论】:

    【解决方案2】:

    您需要在发布模式下编译项目或检查项目属性中的“生成尾调用”选项(如果您通过命令行运行编译器,则使用--tailcalls+)。

    默认情况下,调试模式下不启用尾调用优化。原因是,如果启用了尾调用,您将看不到有关堆栈跟踪的有用信息。因此,默认禁用它们会给您带来更愉快的调试体验(即使在调试模式下,编译器也会优化自调用的尾递归函数,这可以处理大多数情况)。

    【讨论】:

    • 当我听从你的建议时它没有用。我的平台是win7 + VS2010 + .net4.0。
    • 对不起。有用。版本应为发布版本,否则为调试版本。
    • 无论如何都需要延迟。尝试超过 100000,即使在 realese 模式下,您也会看到堆栈溢出。
    • 谢谢!我花了一整天的时间试图弄清楚为什么我总是在 SO 上的后续示例中遇到堆栈溢出
    猜你喜欢
    • 1970-01-01
    • 2015-04-13
    • 1970-01-01
    • 2011-09-29
    • 2015-12-23
    • 1970-01-01
    • 2021-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多