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

发布于 2024-08-13 08:15:58 字数 850 浏览 5 评论 0原文

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

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

我想,我可以使用任何一种情况或尝试捕获来做到这一点。但效率是我主要关心的问题,我想知道以下两种方法中哪一种更有效:

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.

案例方法

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

Say I have some function fn1() in Erlang which returns {ok, Result} if the function was executed successfully and {error, "ErrorReason"} if there was an error.

Now in another function fn2() I call fn1() and I need to check the result of fn1 and proceed only if it is {ok, Result}.

I figured, I can do this using either case or try catch. But Efficiency is my main concern and I'd like to know which of the two methods below is more efficient:

try-catch Method

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 Method

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

じ违心 2024-08-20 08:15:58

你真的想像瘟疫一样尝试和避免 try/catch。这是 Erlang 中非常不常见的习惯用法 - 实际上只在几个特殊情况下使用:

  • 您正在检查用户提供的内容
    输入并且您不能保证
    它就会是“正确的”
  • 如果你有一些东西,
    深度嵌套和成本
    在错误情况下展开它
    太贵了

    • 喜欢 mensia 交易
    • 或者在解析器/词法分析器中

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

您应该对快乐路径进行编程,匹配返回值,如果应用程序偏离您的预期,则让它崩溃。崩溃告诉您遇到了问题并告诉您修复它。

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

第一次编写快乐路径并让它崩溃是非常令人不安的,感觉就像光着身子出去一样,但实际上你很快就习惯了:)

You really want to try and avoid try/catch like the plague. It is a very uncommon idiom in Erlang - really only used in a couple of special cases:

  • where you are checking user-supplied
    input and you have no guarantees that
    it will be 'correct'
  • where you have something which is
    deeply nested and the cost of
    unrolling it on an error condition
    is too expensive

    • like mensia transactions
    • or in parser/lexer's

Try/catch is essential in languages like C++ where the application is unstable in the presence or errors, but Erlang is stable in those circumstances - the process crashes but doens't bring the system down.

You should programme the happy path, match return values and if the application deviates from what you expect then let it crash. The crash tells you you have a problem and tells you to fix it.

The problem with try/catch is that it can simply mask the problem, or even worse, move the eventual crash away from where it should happen (inside the expression you have wrapped) and make it appear elsewhere - where your programming logic expects it to have suceeded = which makes debugging much harder.

Programming the happy path and letting it crash is very disconcerting the first time you do it, it feels like going out with no clothes on, but actually you get used to it real quick :)

埖埖迣鎅 2024-08-20 08:15:58

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

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

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

这里的意图是让 fn2() 抛出错误匹配,如果 fn1( )没有返回确定。你让“上面”的其他人来处理这个问题。例如,这可能会终止您的进程,并让您的主管创建一个新进程。

The case method will be more efficient, as it simply pattern matches, and does not involve building a call stack and stuff.

In both examples you are about to handle the "error" locally, so there is no point in the try catch.What you might see sometimes is something like:

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

Here the intention is that you make fn2() throw a badmatch, if fn1() did not return ok. You let someone else "above" handle the problem. E.g. this might kill your process, and make your supervisor create a new one.

鲜肉鲜肉永远不皱 2024-08-20 08:15:58

您应该始终进行测量以找出此类问题。

您的代码也没有按照您的想法进行操作。

-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,然后终止
如果发生错误,突然退出(为什么)。

事实上,通常您甚至不应该这样做 - 您应该将您的流程链接到另一个流程
那么有问题的进程将会死亡,并且“其他进程将修复错误”。

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

You should always measure to find things like this out.

Your code also does not do what you think it does.

-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}.

Try this out:

 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}]}}

This is because fn1 did not raise an exception
it gebnerated a normal retyurn value {error, a} which
does not pattern match against {ok, Result}

The first version of your code works with a function that either returns a normal value
or raises an exception - you have to write it like this:

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

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

You can't just pump the same function into fn1 and fn2.

If you had the case where the called function had to escape from a deep recursion
then the first method would be more efficient than the second - since you could
immediately exit from a deep recursion by saying throw(...).

So the answer depends upon the nature of the function that you are calling.

Code should always be optimised for beauty and not efficiency - since you have
to maintain the stuff - then it should only be optimised in the rare cases
where it is not fast enough. What needs to be optimised should be identified
by measuring the program (you will always be surprised here :-)

Me, I'd write

{ok,Result} = ...

Actually your first code has a more subtle error

 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.

Think about this. The caught error cases do not themselves handle the error
they just return tuples like {exit, Reason} or {error, Reason} this means that the
next layer up (ie the caller of fn2) will also have to mess around checking
error returns - if this is repeated at all levels the code will be a mess.

The "erlang" way is to have one try-catch at the top of the program and just terminate
abruptly with exit(Why) if an error occurs.

In fact often you should not even do this - you should link your process to another process
then the offending process will die and "the other processes will fix the error".

The exception propagates up the call stack and flies over to the linked processes
for treatment. So we have two types of processes - ones that have no inbuilt error handling
and processes that only do error handling.

双马尾 2024-08-20 08:15:58

在这种情况下,无论哪种方法更有效,您绝对应该使用 case 替代方案,因为它更简洁地描述了正在发生的情况。您的 fn1() 此处返回一个值,指示是否存在成功值或错误。在 case 版本中,您直接与此匹配,而在 try 版本中,您与 success 值匹配,如果返回错误,则会生成错误。它不必要地令人费解,并且隐藏了正在发生的事情,因此这是一种糟糕的编程风格,应该避免。

正如 Gordon 已经指出的那样,尝试一下会捕获比您预期更多的错误,并且可能会掩盖您应该看到的其他真实错误。

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

In this case, irrespective of what is more efficient, you should definitely use the case alternative as it more succinctly describes what is going on. Your fn1() here return a value indicating if there is a successful value or an error. In the case version you directly match against this, while in the try version you match against the success value which will generate an error if an error was returned. It is unnecessarily convoluted and hides what is going on so it is bad programming style and should be avoided.

And as Gordon has already pointed out having a try there will catch more errors than you probably intend and may so mask other real errors which you should see.

The case will also be faster here, but the difference is probably small. The clarity, succinctness and good programming style is much more important!

凌乱心跳 2024-08-20 08:15:58

案例总是会更有效,但差异很小,更重要的是在您的特定案例中更有意义。特别是,哪种方法可以生成更易于理解的代码。请参阅此基准:

%% 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).

The case will always be more efficient, but the difference is small and it's more important what makes more sense in your particular case. Especially, which approach produces more understandable code. See this benchmark:

%% 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).
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文