Хвостовая рекурсия и исключения в 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
Другие вопросы по тегам