【问题标题】:Difference ok and end [Erlang]区别 ok 和 end [Erlang]
【发布时间】:2015-01-11 07:04:00
【问题描述】:

在 Erlang 中用 end 和 ok 结束函数有什么区别?我一直在尝试理解以下代码中的含义:

-module(esOne).
-export([start/1, func/1]).

start(Par) ->
    io:format("Client: I am ~p, spawned by the server: ~p~n",[self(),Par]),
    spawn(esOne, func, [self()]),
    Par ! {onPid, self()},
    serverEsOne ! {onName, self()},
    receiveMessage(),
    ok.

receiveMessage() ->
    receive
        {reply, N} ->
            io:format("Client: I received a message: ~p~n",[N])
    after
        5000->
            io:format("Client: I received no message, i quit~n",[])
    end.

func(Parent)->
    io:format("Child: I am ~p, spawned from ~p~n",[self(),Parent]).

此代码与另一个充当服务器的 .erl 文件结合使用。我设法通过分析给定的服务器文件并复制它的行为来编写这个。首先,我认为 ok 用于结束每个函数,但事实并非如此,因为我不能用 ok 结束 receiveMessage()。然后我想我可以用 end 结束每个函数,但是如果我用 end 替换 ok,start(Par) 会出错。不仅如此,在服务器文件中,我看到 ok 和 end 在函数中用于结束循环。它们的使用方式在我看来是一样的,但它们显然实现了一个单独的功能,因为一个不能被另一个替代。一些澄清将不胜感激。

【问题讨论】:

  • 我在您的示例代码中调整了一些内容以提高可读性。使用io:format 时,您希望使用“~n”而不是“\n”。此外,我们通常使用下划线表示原子like_this_one,使用驼峰式表示VariableNames。这极大地提高了可读性和视觉识别——以至于源代码突出显示在 Erlang 中通常不那么重要。

标签: erlang


【解决方案1】:

两点理解:

  • Erlang 中的某些代码块类型以“结束”结束。所以if ... endcase ... endreceive ... [after N] ... end 等等。当然可以使用“end”作为它自己的原子来代替 OK,但这不是上面发生的事情。

  • Erlang 中的每个函数都会返回一些值。如果您没有明确说明,它会返回最后一个表达式的值。 “=”运算符不像其他语言那样赋值给变量,而是像数学那样赋值给符号,这意味着重新赋值实际上是一个逻辑断言。如果断言失败,进程会抛出异常(这意味着它通常会崩溃)。

当你以“ok”或任何其他原子结束某事时,你提供了一个将返回的已知最终值。您不必对它做任何事情,但是如果您希望调用进程断言该函数已完成或在发生任何异常情况时崩溃,那么您可以:

do_stuff() ->
    ok = some_func().

而不是

do_stuff() ->
    some_func().

如果 some_func() 可能有可能失败的副作用,它通常会返回 ok{error, Reason}(或类似的东西)。通过检查返回是否为ok,我们可以防止调用进程在发生不良情况时继续执行。这是 Erlang 概念“让它崩溃”的核心。基本思想是,如果你调用一个有副作用的函数并且它做了任何意想不到的事情,你应该立即崩溃,因为处理坏数据比根本不继续更糟糕。崩溃将由主管清理,系统将恢复到已知状态,而不是处于副作用失败后留下的任何随机状态。

如果函数的目的是返回一个值,则上述位的一个变体是让“ok”部分出现在元组中。例如,您可以在任何 dict-type 处理库中看到这一点。一些数据返回函数的返回类型为{ok, Value} | {error, Reason} 而不仅仅是Value | {error, Reason} 的原因是为了使模式匹配更自然。

考虑以下 case 子句:

case dict:find(Key, Dict) of
    {ok, Value} ->
        Value;
    {error, Reason} ->
        log(error, Reason),
        error
end.

还有:

case finder(Key, Struct) of
    Value ->
        Value;
    {error, Reason}
        log(error, Reason),
        error
end.

在第一个示例中,我们首先匹配成功条件。但是,在第二个版本中,这是不可能的,因为错误子句永远不会匹配。任何回报都将始终由Value 表示。哎呀。

大多数时候(但不总是)返回值的函数崩溃只会返回该值。对于不带状态但您传入的内容且没有副作用的纯函数尤其如此(例如,dict:fetch/2 直接给出值,或者使调用进程崩溃,让您可以轻松选择要执行的方式事物)。返回值或发出错误信号的函数通常将有效响应包装在 {ok, Value} 中,因此很容易匹配。

【讨论】:

  • 在@zxq9 最后一个示例中,您可以还原子句:将{error,Reason} 放在第一位,Value 放在第二位。只要返回值不能具有{error,Reason} 的形式,它就可以工作。所以{ok,Value}是更好的选择。
  • 是的,它允许您轻松编写{ok,Val} = finder(Key, Struct),当找不到任何内容时会崩溃。
  • @rvirding ...我觉得这是一件好事,但对于一个新来者来说,它的措辞可能听起来不像是一件好事。 ;-)
  • @zxq9 是真的。 :-) 但是当你习惯它时:当函数没有返回你想要的东西时,这是标准的崩溃方式,你将返回值与你想要的东西相匹配。缺少的是能够在比赛中有后卫。
  • @Babyburger 基本上,你明白了。但请记住,ok 是实际返回值,因此它更像 Java 中的 return "ok";。没有它,该值将是函数中最后一个表达式返回的值。在 Erlang 中执行 ok = somefun() 就像在 Java 中使用 if (somefun() != "ok") {throw new SomeException;} 调用它一样,在 Erlang 中调用 somefun(), 就像在 Java 中调用 somefun(); 并让返回值进入垃圾箱。无论如何,你在正确的轨道上。玩弄一下 Erlang,一切都会突然变得如此明显。 :-)
猜你喜欢
  • 2019-07-06
  • 2013-09-06
  • 2013-11-02
  • 1970-01-01
  • 2010-12-23
  • 1970-01-01
  • 2018-12-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多