Хвостовая рекурсия и исключения в F#

Я гуглил целую вечность и до сих пор не могу найти ответ. Насколько я понимаю, F# 3.0, работающий на .NET 4.5, не будет использовать хвостовую рекурсию для рекурсивного метода, если инициатор завернул вызов в блок try/catch и/или try/finally. Какова ситуация, если есть попытка/поймать или попробовать/наконец на несколько уровней вверх по стеку?


person user1563526    schedule 21.11.2012    source источник
comment
Что произойдет, если вы запустите такую ​​функцию?   -  person John Palmer    schedule 21.11.2012


Ответы (1)


Если вы оберните тело некоторой (хвостовой) рекурсивной функции в блок try ... with, то функция больше не будет хвостовой рекурсией, потому что кадр вызова не может быть отброшен во время рекурсивного вызова - он должен оставаться в стеке с зарегистрированный обработчик исключений.

Например, скажем, у вас есть что-то вроде функции 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
person Tomas Petricek    schedule 21.11.2012