如何使重试函数尾递归?

How can I make a retry function tail recursive?

我有一个与 Scott's Railway Oriented Programming 中使用的 Result 类型相似的可区分联合。为了简单起见,这里稍微简化一下:

type ErrorMessage = ErrorMessage of string

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

我有一个相应的模块 ValidationResult,其中包含作用于这些 ValidationResult 的函数,其中一个是允许参数 [=16= 的递归 retryable 函数],如果 ValidationResultError:

,则再次调用(例如从 stdin 读取)
module ValidationResult

    let doubleMap success error = function
         | Success x -> success x
         | Error e -> error e

    let rec retryable errorHandler f =
        let result = f ()
        let retry e =
            errorHandler e
            retryable errorHandler f
        doubleMap id retry result

但它不是尾递归的,我想将其转换为尾递归。我该怎么做?

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

let rec retryable errorHandler f =
    match f() with
    | Success x -> x
    | Error e ->
        errorHandler e
        retryable errorHandler f

F# 编译器以两种不同的方式编译尾递归函数。

  1. 如果函数简单(直接调用自身),则编译成循环
  2. 如果尾递归涉及多个不同的函数(甚至函数值),则编译器使用 .tail IL 指令进行尾调用。这也是尾调用,但由 .NET 运行时处理,而不是由 F# 编译器消除。

在你的例子中,retryable 函数 已经是尾递归的,但它是第二种。大牛的回答很简单,所以就变成了第一种。

但是,您可以保留该函数,因为它是尾递归的。唯一需要注意的是,编译器在 Debug 模式下默认不会生成 .tail 指令(因为它会弄乱调用堆栈),因此您需要显式启用它(在项目选项中,勾选 "Generate tail calls").