Erlang:在这种情况下哪个更有效 - 使用try catch或case语句?

时间:2009-12-02 10:27:17

标签: erlang performance

假设我在Erlang中有一些函数fn1(),如果函数执行成功则返回{ok, Result},如果有错误则返回{error, "ErrorReason"}

现在在另一个函数fn2()中我调用fn1()并且我需要检查fn1的结果并且仅在它是{ok, Result}时继续。

我想,我可以使用case或try catch来做到这一点。但效率是我的主要关注点,我想知道以下两种方法中的哪一种更有效:

try-catch方法

fn2() ->
   try
      {ok, Result} = fn1(),
      %Do something with Result
      ok
   catch
      throw:Term -> Term;
      exit:Reason -> {exit, Reason};
      error:Reason -> {error,{Reason,erlang:get_stacktrace()}}
   end.

case方法

fn2() ->
   Res = fn1(),
   case Res of
      {ok, Result} -> 
         %Do something with Result
         ok;
      {error, Reason} ->
         Reason
   end.

5 个答案:

答案 0 :(得分:10)

你真的想尝试避免像瘟疫一样的尝试/捕获。在Erlang中这是一个非常罕见的习语 - 实际上只用于几个特殊情况:

  • 您正在检查用户提供的内容 输入,你不能保证 这将是'正确'
  • 你有什么东西 深深嵌套和成本 在错误条件下展开它 太贵了
    • 喜欢朱莉亚交易
    • 或解析器/ lexer的

尝试/ catch在C ++这样的语言中是必不可少的,其中应用程序在存在或错误时不稳定,但Erlang在这些情况下是稳定的 - 进程崩溃但不会导致系统崩溃。

您应该编写快乐路径,匹配返回值,如果应用程序偏离您的预期,那么 让它崩溃 。崩溃告诉您有问题并告诉您修复它。

try / catch的问题在于它可以简单地掩盖问题,甚至更糟糕的是,将最终的崩溃从它应该发生的地方移开(在你已经包装的表达式中)并使其出现在其他地方 - 编程逻辑期望它已经成功=这使得调试变得更加困难。

编写快乐路径并让它崩溃是第一次这样做时非常令人不安,感觉就像外出时没穿衣服,但实际上你很快就习惯了它:)

答案 1 :(得分:8)

case方法会更有效,因为它只是模式匹配,并且不涉及构建调用堆栈和东西。

在这两个示例中,您将在本地处理“错误”,因此try catch中没有任何意义。您可能会看到以下内容:

fn2() ->
  {ok, Result} = fn1(),
  %Do stuff with Result
  ok.

这里的意图是你使fn2()抛出坏匹配,如果fn1()没有返回ok。你让别人“上面”处理这个问题。例如。这可能会扼杀你的过程,并让你的主管创建一个新的。

答案 2 :(得分:4)

你应该总是测量以找到这样的东西。

您的代码也没有按照您的想法执行。

-module(glurk).
-compile(export_all).

fn2() ->
   try
      {ok, Result} = fn1(),
      %Do something with Result
      ok
   catch
      throw:Term -> Term;
      exit:Reason -> {exit, Reason};
      error:Reason -> {error,{Reason,erlang:get_stacktrace()}}
   end.

fn1() ->
    {error, a}.

试试这个:

 c(glurk).   
./glurk.erl:6: Warning: variable 'Result' is unused
{ok,glurk}
16> glurk:fn2().
{error,{{badmatch,{error,a}},
        [{glurk,fn2,0},
         {erl_eval,do_apply,5},
         {shell,exprs,6},
         {shell,eval_exprs,6},
         {shell,eval_loop,3}]}}

这是因为fn1没有引发异常 它产生了正常的重新定价值{error,a} 没有模式匹配{ok,Result}

代码的第一个版本使用一个返回正常值的函数 或引发异常 - 你必须这样写:

fn1(....) ->
     ...
     %% success case
     Val;

     %% failure case
     throw(...) | exit(...) | error(...)

你不能只将相同的功能泵入fn1和fn2。

如果遇到被调用函数必须从深度递归中逃脱的情况 然后第一种方法比第二种方法更有效 - 因为你可以 通过说throw(...)立即退出深度递归。

所以答案取决于你所调用的函数的性质。

代码应始终针对美观而非效率进行优化 - 因为您拥有 保持这些东西 - 那么它应该只在极少数情况下进行优化 哪里不够快。应该确定需要优化的内容 通过测量程序(你将永远感到惊讶: - )

我,我会写

{ok,Result} = ...

实际上你的第一个代码有一个更微妙的错误

 fn2() ->
   try
      {ok, Result} = fn1(),
      %Do something with Result
      ok
   catch
      throw:Term -> Term;
      exit:Reason -> {exit, Reason};
      error:Reason -> {error,{Reason,erlang:get_stacktrace()}}
   end.

想一想。捕获的错误情况本身不会处理错误 他们只返回像{exit,Reason}或{error,Reason}这样的元组,这意味着 下一层(即fn2的调用者)也将不得不乱七八糟地检查 错误返回 - 如果在所有级别重复此代码将是一团糟。

“erlang”方式是在程序顶部有一个try-catch,然后终止 如果发生错误,突然退出(为什么)。

事实上,您通常不应该这样做 - 您应该将您的流程链接到另一个流程 那么违规的过程将会消亡,“其他过程将解决错误”。

异常传播到调用堆栈并飞到链接的进程 治疗。所以我们有两种类型的进程 - 没有内置错误处理的进程 和只进行错误处理的进程。

答案 3 :(得分:2)

在这种情况下,无论什么效率更高,您都应该使用case替代方案,因为它更简洁地描述了正在发生的事情。您的fn1()此处返回一个值,指示是否存在成功值或错误。在case版本中,您直接与此匹配,而在try版本中,您与成功值匹配,如果返回错误,则会生成错误。它不必要地复杂化并隐藏正在发生的事情,因此编程风格很差,应该避免使用。

正如戈登已经指出有一个try会有更多的错误,可能会掩盖你应该看到的其他真正的错误。

case在这里也会更快,但差异可能很小。清晰,简洁和良好的编程风格更为重要!

答案 4 :(得分:0)

案例总是会更有效率,但差异很小,而且在您的特定情况下更有意义。特别是,哪种方法产生更易理解的代码。见这个基准:

%% Results:
%% 7> errors:run_normal(100).
%% {9,ok}
%% 8> errors:run_normal(1000).
%% {107,ok}
%% 9> errors:run_normal(10000).
%% {856,ok}
%% 10> errors:run_normal(1000, 10).
%% {263,ok}
%% 11> errors:run_wcatch(10000).
%% {2379,ok}
%% 12> errors:run_wcatch(1000, 10).
%% {401,ok}
%% 18> errors:run_normal_cplx(10000, 50).
%% {7910,ok}
%% 19> errors:run_wcatch_cplx(10000, 50).
%% {10222,ok}
-module(errors).

-compile(export_all).

run_normal(Iterations) ->
    get_result(Iterations, fun() -> normal() end).

run_normal(Iterations, Level) ->
    get_result(Iterations, fun() -> deepnormal(Level) end).

run_wcatch(Iterations) ->
    get_result(Iterations, fun() -> wcatch() end).

run_wcatch(Iterations, Level) ->
    get_result(Iterations, fun() -> deepwcatch(Level) end).

run_normal_cplx(Iterations) ->
    get_result(Iterations, fun() -> normal_complex() end).

run_normal_cplx(Iterations, Level) ->
    get_result(Iterations, fun() -> deepnormal_complex(Level) end).

run_wcatch_cplx(Iterations) ->
    get_result(Iterations, fun() -> wcatch_complex() end).

run_wcatch_cplx(Iterations, Level) ->
    get_result(Iterations, fun() -> deepwcatch_complex(Level) end).

%%------------------------------------------------------------------------------

get_result(Iterations, Fun) ->
    timer:tc(fun() -> run(Iterations, Fun) end).

run(0, _Fun) ->
    ok;
run(Iterations, Fun) ->
    Fun(),
    run(Iterations - 1, Fun).

%%------------------------------------------------------------------------------

normal() ->
    case foo(atom) of
        {ok, atom} -> ok;
        {error, atom} -> ok
    end.

normal_complex() ->
    case foo_cplx() of
        {ok, Res} -> Res;
        {error, Res} -> Res
    end.

deepnormal(Level) ->
    case deepfoo(atom, Level) of
        {ok, atom} -> ok;
        {error, atom} -> ok
    end.

deepnormal_complex(Level) ->
    case deepfoo_cplx(Level) of
        {ok, Res} -> Res;
        {error, Res} -> Res
    end.

wcatch() ->
    try
        {ok, atom} = foothrow(atom)
    catch
        throw:{error, atom} -> ok
    end.

wcatch_complex() ->
    try
        {ok, _Res} = foothrow_cplx()
    catch
        throw:{error, Res} -> Res
    end.

deepwcatch(Level) ->
    try
        {ok, atom} = deepfoothrow(atom, Level)
    catch
        throw:{error, atom} -> ok
    end.

deepwcatch_complex(Level) ->
    try
        {ok, _Res} = deepfoothrow_cplx(Level)
    catch
        throw:{error, Res} -> Res
    end.

%%------------------------------------------------------------------------------

foo(Arg) -> {error, Arg}.

foothrow(Arg) -> throw({error, Arg}).

deepfoo(Arg, 0) -> {error, Arg};
deepfoo(Arg, Level) -> deepfoo(Arg, Level - 1).

deepfoothrow(Arg, 0) -> throw({error, Arg});
deepfoothrow(Arg, Level) -> deepfoothrow(Arg, Level - 1).


foo_cplx() -> {error, {<<"Some">>, "Complex", data}}.

foothrow_cplx() -> throw({error, {<<"Some">>, "Complex", data}}).

deepfoo_cplx(0) -> {error, {<<"Some">>, "Complex", data}};
deepfoo_cplx(Level) -> deepfoo_cplx(Level - 1).

deepfoothrow_cplx(0) -> throw({error, {<<"Some">>, "Complex", data}});
deepfoothrow_cplx(Level) -> deepfoothrow_cplx(Level - 1).