在 [Erlang 0009] Erlang 杂记 第30条,我提到了关于一个关于if语句的小陷阱:
30.if语句会对Guard子句做catch,所以 if 1/0 ->a; true ->b end.的返回值是b而不是抛出异常
不相信?打开Erlang Shell操练一下看看:
Eshell V5.9 (abort with ^G)
1> F = fun(X) -> if 1/0 -> a; true -> b end end.
#Fun<erl_eval.6.111823515>
2> F(1).
b
3> G=1/0 , if G->a; true ->b end.
** exception error: bad argument in an arithmetic expression
in operator '/'/2
called as 1 / 0
上面第三条语句给出了应对策略,就是把条件的计算放在if外,这样如果有异常就会抛出来,不会在异常的情况下进入了你不期望的逻辑分支;
为什么会有这样的结果?Erlang官方文档是说的滴水不漏的:
if
GuardSeq1 ->
Body1;
...;
GuardSeqN ->
BodyN
end
The branches of an if-expression are scanned sequentially until a guard sequence GuardSeq which evaluates to true is found. Then the corresponding Body (sequence of expressions separated by ',') is evaluated.
if表达式的执行会逐个扫描guard子句,直到有运算结果为true的时候停下来,我们上面的特例1/0的执行结果是一个异常,并不是true,这里异常只是作为一个特殊的返回值来看待的,所以会继续扫描后续Guard子句! 事情还没有完,在Erlang学习网站Learn you some Erlang上,在谈到Guard的时候,作者做了一个特别的备注:
Note: I've compared
,
and ;
in guards to the operators andalso
and orelse
. They're not exactly the same, though. The former pair will catch exceptions as they happen while the latter won't. What this means is that if there is an error thrown in the first part of the guard X >= N; N >= 0
, the second part can still be evaluated and the guard might succeed; if an error was thrown in the first part of X >= N orelse N >= 0
, the second part will also be skipped and the whole guard will fail.However (there is always a 'however'), only
andalso
and
orelse
can be nested inside guards. This means
(A orelse B) andalso C
is a valid guard, while
(A; B), C
is not. Given their different use, the best strategy is often to mix them as necessary.
Traceback: http://learnyousomeerlang.com/syntax-in-functions#guards-guards
我认为这里作者的理解是有偏差的,他把, ;和andalso orelse 看作同一层次上的东西,并比较了它们的异同,并得出结论:前者会做异常捕获后者不会,即当X>=N;N>0这个表达式前半部分出现异常的情况下后半部分还会进行运算;而对于X>=N orelse N>0 前半部分异常之后后面的部分就会跳过.并且作者总结了一下andalso orelse 是可以在Guard嵌套的,但是,;是不可以的. 那么它们的层次关系是什么样的呢?看下面的图示:
上图是对官方文档的重新组织表达
A guard sequence is a sequence of guards, separated by semicolon (;). The guard sequence is true if at least one of the guards is true. (The remaining guards, if any, will not be evaluated.)
Guard1;...;GuardKA guard is a sequence of guard expressions, separated by comma (,). The guard is true if all guard expressions evaluate to true.
GuardExpr1,...,GuardExprNThe set of valid guard expressions (sometimes called guard tests) is a subset of the set of valid Erlang expressions. The reason for restricting the set of valid expressions is that evaluation of a guard expression must be guaranteed to be free of side effects. Valid guard expressions are:
- the atom true,
- other constants (terms and bound variables), all regarded as false,
- calls to the BIFs specified below,
- term comparisons,
- arithmetic expressions,
- boolean expressions, and
- short-circuit expressions (andalso/orelse).
Traceback:http://www.erlang.org/doc/reference_manual/expressions.html#id79005 也就是说Guard sequence是包含多个Guard子句,Guard子句之间使用分号分隔,只要Guard子句中有一个运算结果为true,该表达式的值就为true,且后续子句就不再执行;Guard子句包含若干Guard expressions,所有表达式运算结果都为true的情况下,Guard的结果为true;Erlang 中有效的Guard表达式其中包括短路判断表达式short-circuit(andalso/orelse).作者的结论是正确的,但是把Guard子句分隔符,表达式分隔符,短路判断语句混为一谈了;瑕不掩瑜,我会发邮件给作者纠正一下. 通过邮件交流,我明白了作者为什么要这样做:Learn you some Erlang的定位就是把学习Erlang这件事变得有趣,容易理解,降低学习曲线.直观的描述比严格的描述效果可能会更好一些;作者回复如下:
Ah, I understand what you mean, you're right there. If LYSE were to be more of a reference manual, I would definitely update the definition to reflect this. However, I feel it might be going against the tone to go with such strict definitions. I made the same choice when it came to explaining that 'functions end in a .' and that module attributes do the same. I could have used the broader distinction between forms and expressions, that ',' act as expression separators, ';' act as branch separators, and that '.' terminate forms (do not separate them), but that the Erlang shell breaks the rules by using '.' as a way to end expressions, which isn't valid by the modules' definitions. However, while the strict definition is 100% true, it isn't the easiest thing when it comes to learning, in my opinion. My definition wouldn't work for someone writing a parser, and it wouldn't work for someone who wants to understand the absolute truth about the language, I however feel it helps in understanding the concept of guards when you have never encountered it before. It's a bit how we could teach children that pi is '3.1416' and let them use this as a notion; they need not to know that pi is irrational, that it has an infinite amount of digits, etc. You may disagree with me on the approach, but I do believe it works rather well. By the way, thanks for writing me about this :) I'm enjoying the discussion. Regards, Fred.
说到这里,我们也就明白了,其实不是if语句的小陷阱而是Guard设计如此;那么看看下面的例子吧: F= fun(X) when 1/0 -> X; (X) when X >0 -> X end . 这个fun 第一个子句存在一个明显的异常,那么这个方法的执行结果会是什么样的?注意这也是Guard哦!