尾递归和F#中的异常递归、异常

2023-09-04 02:38:00 作者:温稚晚

我一直在使用Google的年龄,但仍无法找到答案。从我的理解F#3.0其他计算机上的.NET 4.5,如果调用已包装的调用在一个try / catch和/或尝试了递归方法将不使用尾递归/ finally块。这是什么情况,如果有一个try / catch或尝试/最后几级堆栈?

解决方案

如果您在尝试 ... 与块,然后该功能不是尾递归了,因为调用帧不能递归调用过程中被丢弃 - 它需要留在堆栈注册的异常处理程序。

举例来说,假设你有类似 ITER 功能列表

 让REC ITER˚F列表=
  尝试
    匹配列表
    | []  - > ()
    | X :: XS  - > ˚FX; ITER˚FXS
  随e  - >
    printfn失败:%s的e.Message
 
W chuanqi的博客 CSDN博客

当你调用 ITER F [1; 2; 3] 然后它会创建4异常处理程序嵌套堆栈帧(如果您添加重新抛出分支,那么它实际上将打印错误消息4倍)。

您不能真正增加的异常处理程序不破坏尾递归。但是,您通常不需要嵌套的异常处理程序。所以最好的解决方法是重写功能,使得它并不需要处理的每递归调用异常:

 让ITER˚F列表=
  让REC循环列表=
    匹配列表
    | []  - > ()
    | X :: XS  - > ˚FX;环XS
  尝试循环列表
  随e  - > printfn失败:%s的e.Message
 

这有一点不同的意义 - 但它不创建嵌套的异常处理程序和循环也可以完全尾递归。

另一种选择是增加异常处理只在身体的不包括的尾递归调用。实际上,能在这个例子中抛出异常的唯一的事情为f 调用;

 让REC ITER˚F列表=
  匹配列表
  | []  - > ()
  | X :: XS  - >
    尝试
      ˚Fx
    随e  - >
      printfn失败:%s的e.Message
    ITER˚FXS
 

I've been googling for ages and still can't find the answer. From what I understand F# 3.0 runnning on .NET 4.5 will not use tail recursion for a recursive method if the invoker has wrapped the call in a try/catch and/or try/finally block. What is the situation if there is a try/catch or try/finally a few levels up the stack?

解决方案

If you wrap the body of some (tail) recursive function in a try ... with block then the function is not tail recursive anymore, because the call frame cannot be discarded during the recursive call - it needs to stay in the stack with a registered exception handler.

For example, say you have something like iter function for List:

let rec iter f list =
  try
    match list with
    | [] -> ()
    | x::xs -> f x; iter f xs
  with e ->
    printfn "Failed: %s" e.Message

When you call iter f [1;2;3] then it will create 4 nested stack frames with exception handlers (and if you added rethrow into the with branch, then it would actually print the error message 4 times).

You cannot really add exception handlers without breaking tail-recursion. However, you do not usually need nested exception handlers. So the best solution is to rewrite the function so that it does not need to handle exceptions in every recursive call:

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

This has a little different meaning - but it does not create nested exception handlers and loop can still be fully tail recursive.

Another option would be to add exception handling only over the body excluding the tail-recursive call. Realistically, the only thing that can throw an exception in this example is the call to f;

let rec iter f list =
  match list with
  | [] -> ()
  | x::xs -> 
    try
      f x
    with e ->
      printfn "Failed: %s" e.Message
    iter f xs