2012-11-21 68 views
8

我一直在谷歌搜索了很长时间,仍然无法找到答案。根据我的理解,如果调用者已将调用封装在try/catch和/或try/finally块中,则在.NET 4.5上运行的F#3.0将不会使用递归方法的尾递归。如果尝试/捕捉或尝试/最终在堆栈上的几个级别是什么情况?F中的尾递归和异常#

+0

如果你运行这样的功能会发生什么? –

回答

14

如果你包的一些(尾)身体递归函数在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个嵌套堆栈帧(如果你添加rethrowwith分支,那么它实际上会打印错误消息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