Хвостовая рекурсия и исключения в F#
Я гуглил целую вечность и до сих пор не могу найти ответ. Из того, что я понимаю, запуск F# 3.0 в.NET 4.5 не будет использовать хвостовую рекурсию для рекурсивного метода, если вызывающий обернул вызов в блок try / catch и / или try / finally. Какова ситуация, если есть попытки / поймать или попытаться / наконец несколько уровней вверх по стеку?
1 ответ
Если вы оберните тело некоторой (хвостовой) рекурсивной функции в try
... with
block, то функция больше не является хвостовой рекурсией, потому что кадр вызова не может быть отброшен во время рекурсивного вызова - он должен оставаться в стеке с зарегистрированным обработчиком исключений.
Например, скажем, у вас есть что-то вроде 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 вложенных стековых фрейма с обработчиками исключений (и если вы добавили rethrow
в with
ветвь, тогда это фактически напечатало бы сообщение об ошибке 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