Нет хвостового рекурсивного кода в блоке try catch?

Я читаю урок Эрланга в http://learnyousomeerlang.com/errors-and-exceptions

Я не понимаю эту часть:

выражение между try и of считается защищенным. Это означает, что любое исключение, происходящее в этом вызове, будет поймано.

и

защищенная часть исключения не может быть хвостовой способный к повторному использованию.

[...]

помещая рекурсивные вызовы между of и catch, вы не находитесь в защищенной части, и вы выиграете от оптимизации последнего вызова.

таким образом, мы не можем поместить рекурсивные вызовы в часть, где улавливаются исключения ? Тогда в чем смысл блока try catch ?

и ниже на странице у нас есть пример с хвостовой рекурсивной функцией в защищенном разделе ...

has_value(Val, Tree) ->
  try has_value1(Val, Tree) of
    false -> false
  catch
    true -> true
  end.

has_value1(_, {node, 'nil'}) ->
  false;
has_value1(Val, {node, {_, Val, _, _}}) ->
  throw(true);
has_value1(Val, {node, {_, _, Left, Right}}) ->
  has_value1(Val, Left),
  has_value1(Val, Right).

означает ли он, что нам нужно использовать функцию для обертывания хвостового рекурсивного кода в функцию, когда мы находимся в защищенной части try catch ?

2 ответов


поэтому мы не можем поместить рекурсивные вызовы в ту часть, где исключения поймали ? Тогда в чем смысл блока try catch ?

функция не может рекурсивно вызывать себя внутри try; или, скорее, оптимизация хвоста не произойдет, если это произойдет. Когда вы используете try, вы должны иметь возможность вернуться к catch блокировать в любой точке стека вызовов. Это означает, что должен быть быть стек вызовов. Если хвост вызов оптимизация используется, нет вызовов функций, потому что теперь они просто циклы. Не к чему возвращаться. Таким образом, рекурсия внутри try блок должен действительно рекурсия.

точка такая же, как исключения в большинстве языков. Тот факт, что вы не можете напрямую рекурсировать, немного раздражает, но, конечно, не удаляет утилиту обработки исключений, потому что:

означает ли он, что нам нужно использовать функцию для обертывания хвоста рекурсивно код в функцию, когда мы находимся в защищенной части try catch ?

да. Все, что требуется, это одна дополнительная функция, и вы можете использовать try просто отлично, и все равно получить преимущества TCO. Пример:

% No TCO
func() ->
  try
    func()
  catch _ ->
    ok
  end.

% TCO
func() ->
  try
    helper()
  catch _ ->
    ok
  end.

helper() -> helper().

Я не уверен, есть ли простой способ определить, случайно ли вы рекурсируете, когда ожидаете, что TCO произойдет. Вы, вероятно, просто должны быть бдительны при использовании try.


если вы хотите оптимизировать tail-call, этот вызов должен быть вне предложения try-catching. Вы можете использовать конструкцию

your_fun(...) ->
   ...
   try ... of               <--- make notice of `of`
      ... ->
         some_call(...)
   catch
      ...
   end.

или просто сделать этот вызов после предложения try.

в вашем коде это вызов has_value1(Val, Right). оптимизирован, потому что это последний вызов функции. Это не имеет значения, если он называется внутри try блок в этом случае. И исключение используется только для обеспечения скорейшего выхода из этой функции и простой обработки результата.

это может быть переписан без исключений, но с использованием ручной обработки стека:

has_value(Val, Tree) ->
  has_value(Val, [Tree]).

has_value1(_, []) ->
  false;
has_value1(Val, [{node, 'nil'} | Stack]) ->
  has_value1(Val, Stack);
has_value1(Val, [{node, {_, Val, _, _}} | _]) ->
  true;
has_value1(Val, [{node, {_, _, Left, Right}} | Stack]) ->
  has_value1(Val, [Left, Right | Stack]).