如何使重试函数尾递归?

时间:2016-05-03 13:19:17

标签: recursion f# tail-recursion

我有一个与Scott's Railway Oriented Programming中使用的Result类型相似的歧视联盟。为简单起见,这里稍作简化:

type ErrorMessage = ErrorMessage of string

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

我有一个相应的模块ValidationResult,其中包含作用于这些ValidationResult的函数,其中一个是递归retryable函数,允许参数f: unit -> 'a,如果stdinValidationResult,则再次调用(例如从Error阅读):

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

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

2 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:2)

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

  1. 如果函数很简单(直接调用自身),那么它将被编译成循环
  2. 如果尾递归涉及多个不同的函数(甚至函数值),则编译器使用.tail IL指令进行尾调用。这也是一个尾调用,但由.NET运行时处理,而不是由F#编译器消除。
  3. 在您的情况下,retryable函数 已经是尾递归,但它是第二种。丹尼尔的回答使其变得简单,使其成为第一种。

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