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