Tail recursion and exceptions in F #

I searched the Internet for a long time and still can not 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 invoker wrapped the call in a try / catch and / or try / finally block. What is the situation if there is a try / trick or try / finally several levels up the stack?

+8
tail-recursion f #
source share
1 answer

If you wrap the body with some (tail) recursive function in the try ... with block, then the function is no longer tail recursive, because the call frame cannot be dropped during the recursive call - it needs to be stored on the stack with the registered exception handler.

For example, let's say you have something like iter for a List :

 let rec iter f list = try match list with | [] -> () | x::xs -> fx; 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 add rethrow to the with branch, it will actually display an error message 4 times).

You cannot add exception handlers without breaking tail recursion. However, you usually do not need nested exception handlers. Therefore, the best solution is to rewrite the function so that it does not have to handle exceptions in all recursive calls:

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

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

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

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

All Articles