F#中的尾递归和异常

时间:2012-11-21 10:58:06

标签: .net f# tail-recursion

我一直在谷歌搜索多年,仍然找不到答案。根据我的理解,如果调用者在try / catch和/或try / finally块中包装了调用,那么在.NET 4.5上运行的F#3.0将不会对递归方法使用尾递归。如果有一个try / catch或try / finally几个级别的堆栈会是什么情况?

1 个答案:

答案 0 :(得分:14)

如果在try ... with块中包装某个(尾部)递归函数的主体,则该函数不再是尾递归,因为在递归期间不能丢弃调用帧call - 它需要使用已注册的异常处理程序保留在堆栈中。

例如,假设您有iter的{​​{1}}函数:

List

当你调用let rec iter f list = try match list with | [] -> () | x::xs -> f x; iter f xs with e -> printfn "Failed: %s" e.Message 时,它将创建4个带有异常处理程序的嵌套堆栈帧(如果你将iter f [1;2;3]添加到rethrow分支中,那么它实际上会打印错误消息4次)。

在不破坏尾递归的情况下,您无法真正添加异常处理程序。但是,您通常不需要嵌套的异常处理程序。因此,最好的解决方案是重写函数,以便它不需要在每次递归调用中处理异常:

with

这有一些不同的含义 - 但它不会创建嵌套的异常处理程序,let iter f list = let rec loop list = match list with | [] -> () | x::xs -> f x; loop xs try loop list with e -> printfn "Failed: %s" e.Message 仍然可以完全尾递归。

另一种选择是仅在主体上添加异常处理排除尾递归调用。实际上,在这个例子中唯一能引发异常的是调用loop;

f