10

我正在阅读http://learnyousomeerlang.com/errors-and-exceptions上的 Erlang 课程

我不明白这部分:

据说 try 和 of 之间的表达式是受保护的。这意味着该调用中发生的任何类型的异常都将被捕获。

异常的受保护部分不能是尾递归的。

[...]

通过将您的递归调用放在 of 和 catch 之间,您不会处于受保护的部分,您将受益于 Last Call Optimisation。

所以我们不能把递归调用放在异常被捕获的部分吗?那么 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 的受保护部分时,我们需要使用一个函数将尾递归代码包装到一个函数中吗?

4

2 回答 2

15

所以我们不能把递归调用放在异常被捕获的部分吗?那么 try catch 块有什么意义呢?

函数不能在 a 内递归调用自身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

于 2013-07-09T23:59:46.050 回答
0

如果要优化尾调用,则该调用必须在 try-catch 子句之外。你可以使用建筑

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]).
于 2013-07-10T01:45:05.440 回答