Recursion 如何使重试函数尾部递归?

Recursion 如何使重试函数尾部递归?,recursion,f#,tail-recursion,Recursion,F#,Tail Recursion,我有一个与中使用的结果类型类似的有区别的并集。为了简单起见,这里稍微简化了一下: type ErrorMessage = ErrorMessage of string type ValidationResult<'a> = | Success of 'a | Error of ErrorMessage 但它不是尾部递归的,我想把它转换成这样。我该怎么做呢?只需删除对doubleMap的调用即可: let rec retryable errorHandler f =

我有一个与中使用的
结果
类型类似的有区别的并集。为了简单起见,这里稍微简化了一下:

type ErrorMessage = ErrorMessage of string

type ValidationResult<'a> =
    | Success of 'a
    | Error of ErrorMessage

但它不是尾部递归的,我想把它转换成这样。我该怎么做呢?

只需删除对
doubleMap
的调用即可:

let rec retryable errorHandler f =
    match f() with
    | Success x -> x
    | Error e ->
        errorHandler e
        retryable errorHandler f
F#编译器以两种不同的方式编译尾部递归函数

  • 如果函数很简单(直接调用自身),则将其编译为循环
  • 如果尾部递归涉及多个不同的函数(甚至函数值),则编译器使用
    .tail
    IL指令执行尾部调用。这也是一个尾部调用,但由.NET运行时处理,而不是由F#编译器消除
  • 在您的例子中,
    retryable
    函数已经是尾部递归的,但它是第二种。丹尼尔的回答使它变得足够简单,因此它成为第一种


    但是,您可以保留现有的函数,它将是尾部递归的。唯一需要注意的是,默认情况下,编译器不会在调试模式下生成
    .tail
    指令(因为它会弄乱调用堆栈),因此您需要显式地启用它(在项目选项中,选中“生成tail调用”)。

    AFAIK,函数本身就是tail递归函数,但编译器不会将其编译成循环,因为它使用嵌套函数-因此您需要在项目选项中启用“尾部调用”-这样,它将生成带有实际尾部调用指令的.NET代码。但是如果您在Windows或.NET Core上运行,JIT甚至不需要实际的尾部调用指令,无论如何,它都会优化尾部调用。@TomasPetricek我想如果我将代码反编译成C,并看到
    while(true)
    循环,这意味着我的代码是尾部递归的,这不会发生在我的
    doubleMap
    version@rexcfnghk我在回答中补充了一些细节。只是好奇,有没有办法在保持调用
    doubleMap
    的同时仍然使其尾部递归?我还没有测试过它,但根据Tomas的说法,它应该是尾部递归的。你试过发布版本吗?在debug builds.IMO中默认情况下禁用尾部调用,没有内部函数更容易理解。我想如果我将代码反编译为C#,并看到
    while(true)
    循环,这意味着我的代码是尾部递归的,这在我的
    doubleMap
    版本中不会发生。了解这一点非常有用!
    let rec retryable errorHandler f =
        match f() with
        | Success x -> x
        | Error e ->
            errorHandler e
            retryable errorHandler f