【问题标题】:why this guard don't throw exception?为什么这个守卫不抛出异常?
【发布时间】:2012-08-14 22:58:17
【问题描述】:

考虑以下代码:

    -module(abc).
    -export([f/1]).
    f(X) when (X==0) or (1/X>2) -> X+100;
    f(X) ->X.

和 abc:f(0)。得到结果 0 ,但是为什么 1/X 不抛出异常??

【问题讨论】:

  • Erlang comments 说:"建议您永远不要使用'and'和'or'运算符,并在','和'时避免使用'andalso'和'orelse'; '会做你需要的。”(但没有给出理由。)

标签: erlang


【解决方案1】:

erlang 文档的Guard Sequences 部分说:

如果算术表达式、布尔表达式、短路表达式或对保护 BIF 的调用失败(因为参数无效),则整个保护失败。如果守卫是守卫序列的一部分,则将评估序列中的下一个守卫(即下一个分号后面的守卫)。

换句话说,守卫中的异常被视为如果守卫返回 false 而不引发异常。守卫的评估与正常的 erlang 表达式略有不同。

当您调用 abc:f(0) 时,会计算表达式 (0==0) or (1/0>2)。这个表达式“失败”,因为除以零,所以守卫不匹配,下一个子句被评估以给出0的答案。

如果您希望这种情况返回 100,您有两个选择:使用保护序列或使用短路布尔运算符。这些将是

f(X) when X==0; 1/X>2 -> X + 100;
f(X) -> X.

和

f(X) when X==0 orelse 1/X>2 -> X + 100;
f(X) -> X.

分别。两种编写方式都会将X==0 评估为单独的异常,如果结果为真,则不执行1/X>2。

【讨论】:

  • 但是守卫如何对待1/X>2,当X=0时,为什么不抛出算术异常呢??
  • 因为第一个守卫返回 true,并且它是 OR 评估,那么,它不会打扰第二个守卫 - 我猜?
  • @Muzaaya Joshua:不,你说的是orelse
  • 守卫的评估不应该有任何副作用,就像模式匹配一​​样。您寻求的算术异常被捕获并转换为“假”值以隐藏副作用。
【解决方案2】:

有关文档,请参阅 here。挑剔并准确:

一个守卫由一系列测试组成,而不是表达式,一个测试要么成功要么失败,如果测试中有错误它不会生成一个异常它就会失败。

在一个守卫中,你可以有一个守卫序列,它是由;分隔的守卫序列,如果其中一个守卫成功,则整个守卫序列成功。所以; 分隔了备用守卫。

在一个守卫中可以有一系列由,分隔的守卫测试,守卫中的所有测试都必须成功,整个守卫才能成功。所以最一般的守卫是:

f(...) when <test11>, <test12> ; <test21>, <test22> ; ... ->

那么布尔运算符呢?它们与测试有什么关系,, 和 ;?在警戒测试中使用布尔运算符是完全合法的,它们的行为符合预期,但是它们不与使用 , 和;。尤其是在失败方面。所以布尔表达式 &lt;test11&gt; and &lt;test12&gt; 只是 一个 测试,而不是两个的序列。更重要的是&lt;test11&gt; or test&lt;21&gt;(或使用orelse)仍然是一个警卫测试,而不是两个警卫的序列。因此&lt;test11&gt; 中的错误将使整个警卫失败。而使用&lt;test11&gt; ; &lt; test21&gt; 时,&lt;test11&gt; 中的错误将无法通过保护测试,而备用保护&lt;test21&gt; 将被尝试。

这就是@ShiDoiSi 提到的评论中建议背后的原因。您可以使用其中任何一种,但请注意它们的含义以及它们的行为方式。 记住:守卫由测试而不是表达式组成。

附:关于为什么会这样的一些历史。这真的很简单:早在我们有布尔运算符之前我们就有了守卫,所以当我们最终得到布尔运算符时,守卫的语义已经很好地定义了,现在改变已经太晚了。虽然在守卫中允许布尔表达式允许您编写更精确的守卫,但它确实倾向于隐藏守卫的真实性质。

【讨论】:

    猜你喜欢
    • 2014-07-09
    • 1970-01-01
    • 2017-07-03
    • 1970-01-01
    • 1970-01-01
    • 2017-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多