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?
Tail recursion is defined as a recursive function in which the recursive call is the last statement that is executed by the function. So basically nothing is left to execute after the recursion call. For example the following C++ function print() is tail recursive.
Tail Recursion: If a recursive function calling itself and that recursive call is the last statement in the function then it's known as Tail Recursion. After that call the recursive function performs nothing.
In tail recursion, there is no other operation to perform after executing the recursive function itself; the function can directly return the result of the recursive call. In non-tail recursion, some operations need to be performed using the returned value of the recursive call.
Tail Recursion Elimination is a very interesting feature available in Functional Programming languages, like Haskell and Scala. It makes recursive function calls almost as fast as looping.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With