我已经用谷歌搜索了很长时间,但仍然找不到答案。据我了解,如果调用者将调用包装在 try/catch 和/或 try/finally 块中,则在 .NET 4.5 上运行的 F# 3.0 不会将尾递归用于递归方法。如果有一个 try/catch 或 try/finally 在堆栈上几级,情况会怎样?
问问题
725 次
1 回答
16
如果将某些(尾)递归函数的主体包装在try
...with
块中,则该函数不再是尾递归,因为在递归调用期间不能丢弃调用帧 - 它需要保留在堆栈中并注册异常处理程序。
例如,假设您有类似iter
功能的东西List
:
let rec iter f list =
try
match list with
| [] -> ()
| x::xs -> f x; iter f xs
with e ->
printfn "Failed: %s" e.Message
当您调用iter f [1;2;3]
时,它将创建 4 个带有异常处理程序的嵌套堆栈帧(如果您添加rethrow
到with
分支中,那么它实际上会打印 4 次错误消息)。
在不破坏尾递归的情况下,您无法真正添加异常处理程序。但是,您通常不需要嵌套异常处理程序。所以最好的解决方案是重写函数,使其不需要在每次递归调用中处理异常:
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
;的调用。
let rec iter f list =
match list with
| [] -> ()
| x::xs ->
try
f x
with e ->
printfn "Failed: %s" e.Message
iter f xs
于 2012-11-21T11:24:13.443 回答