8

我已经用谷歌搜索了很长时间,但仍然找不到答案。据我了解,如果调用者将调用包装在 try/catch 和/或 try/finally 块中,则在 .NET 4.5 上运行的 F# 3.0 不会将尾递归用于递归方法。如果有一个 try/catch 或 try/finally 在堆栈上几级,情况会怎样?

4

1 回答 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 个带有异常处理程序的嵌套堆栈帧(如果您添加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
于 2012-11-21T11:24:13.443 回答