Guard
Guard 第一版翻译为「断言」,第二版翻译为关卡。
Guard 是一种结构,由一系列「guard expression」组成,用逗号「,」分隔,是模式匹配的一种扩展,用于提高模式匹配的作用,通过使用 Guard 可以对某个模式里的变量执行简单的测试和比较。
只有在所有的「guard expression」值为 true 时,guard 才为 true。
由于模式匹配无副作用,所以为了保证 guard 的执行不带副作用,在 guard 内是不能调用用户自定义函数的。
Guard Sequence
「Guard sequence」指单一或一系列的关卡,用分号「;」分隔,「guard sequence」只要其中一个 guard 值为 true,整个「guard sequence」的值为 true。
Guard Example
首先我们先来区分一下布尔表达式「and, or」和短路布尔表达式「andalso, orelse」的区别:
- 在布尔表达式中,and 和 or 两边的表达式都要被求值,即使整个表达式的值只需要第一个表达式就能确定也是如此。比如: X =:= 2 or X =:= 3 当 X 等于 2 时,会先判断 X =:= 2 在判断 X =:= 3,然后返回 true
- 在短路布尔表达式中,如果第一个表达式的值就可以确定整个表达式的值,那么之后的表达式就不会被执行。比如: X =:= 2 orelse X =:= 3 当 X 等于 2 时,就只判断 X =:= 2 然后返回 true
网上的资料都说: Guard 中的逗号「,」相当于 Erlang 中的布尔表达式 and,「guard sequence」中的分号「;」相当于布尔表达式中的 or,我做了个测试,发现有点不一样
这里来看个代码示例:
test_guard_zero(X) when not ((X rem 0) =:= 0) ->
io:format("0 can't pass~n");
test_guard_zero(_X) ->
io:format("zero~n").
test_guard_andalso(X) when not(X =/= 0 andalso ((X / 0) =:= 0)) ->
io:format("andalso 0 can pass~n").
test_guard_orelse(X) when X =:= 0 orelse ((10 rem X) =:= 0) ->
io:format("orelse 0 can pass~n").
test_guard_and(X) when not(X =/= 0 and ((X / 0) =:= 0)) ->
io:format("and when X = 0 will exception error~n").
test_guard_or(X) when X =:= 0 or ((10 rem X) =:= 0) ->
io:format("or when X = 0 will exception error.~n").
%% test_guard_comma(X) when not(X =/= 0, (10 rem X =:= 0)) ->
%% 这里发现 not 后面不能跟多个表达式,上面这一条语句语法错误,记录一下
test_guard_comma(X) when X =/= 0, (10 rem X =:= 0) ->
io:format("comma useless ~n");
test_guard_comma(_X) ->
io:format("can't judge").
test_guard_semicolon(X) when X =:= 0; X rem 0 =:= 0 ->
io:format("semicolon 0 can pass~n").
首先我们需要先证明上面所说的布尔表达式和短路布尔表达式的区别
下面看上述代码执行结果,为了节省空间,省略了打印的 ok:
1> c(test_guard).
{ok,test_guard}
2> test_guard:test_guard_zero(0).
zero
这一条是为了证明当 Guard 中出现异常时当前匹配会直接失败,而不是返回 false「如果返回 false,上面应该打印『0 can’t pass』」
3> test_guard:test_guard_andalso(0).
andalso 0 can pass
4> test_guard:test_guard_orelse(0).
orelse 0 can pass
这两条输出结果证明了「andalso, orelse」当第一个表达式的值就是真值时,后面的表达式不会被求值「如果后面的表达式被求值会出现异常,然后根据第一条输出结果可知当前匹配会失败,会出现没有匹配的函数异常」
5> test_guard:test_guard_and(0).
** exception error: no function clause matching
test_guard:test_guard_and(0) (test_guard.erl, line 28)
6> test_guard:test_guard_or(0).
** exception error: no function clause matching
test_guard:test_guard_or(0) (test_guard.erl, line 30)
这两条输出表明「and, or」会求出所有表达式的值,所以由于除 0 异常导致当前匹配失败,从而整个函数的模式匹配失败,造成「no function clause matching」的异常
以上证明了短路布尔表达式和布尔表达式的区别
接下来看我们要讨论的正题:
7> test_guard:test_guard_comma(0).
can't judgeok
8> test_guard:test_guard_semicolon(0).
semicolon 0 can pass
ok
首先 test_guard_comma 这条输出结果证明不了任何事,因为不知道是因为短路求值的 false 导致进入第二个分支,还是因为非短路求值第二个表达式异常造成匹配失败从而进入到第二个分支。而由于 not 后面不能跟逗号连接的表达式,所以无法像上面 and 和 andalso 那样来匹配。
然后我们看分号表达式的输出结果,发现分号的作用在这里相当于 orelse,因为上面已经知道了如果是 or 这里会发生异常。
所以这里的分号和逗号代表什么,暂时我还不确定,等搞清楚了再来补充。
modified at 23:40, 27 May
这里还是要理清楚 Guard 和「Guard Sequence」的概念,
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, are not evaluated.)
.
Guard1;…;GuardK
.
A guard is a sequence of guard expressions, separated by comma (,). The guard is true if all guard expressions evaluate to true.
.
GuardExpr1,…,GuardExprN
.
The set of valid guard expressions (sometimes called guard tests) is a subset of the set of valid Erlang expressions.
Guard 是由一系列「Guard Expression」组成,之间用逗号分隔,所有「Guard Expression」值为 true 的时候,整个 Guard 值才为 true,所以 Guard 中的每个表达式都需要求值之后才能知道 Guard 的值;
而「Guard Sequence」是由一系列 Guard 组成,任何一个 Guard 的值为 true, 整个「Guard Sequence」值就为 true,所以不需要全部求值。
所以这里的 逗号 和 分号 是有自己的含义,跟布尔表达式和短路布尔表达式没有任何关系
modified at 08:36 30, May
为什么会记得 Erlang Guard 和布尔表达式的类比?
今天在「Erlang 趣学指南」3.2 卫语句中找到了:
在卫表达式中,逗号的作用和操作符 andalso 类似,分号和 orelse 类似
但是不完全相同:卫语句的 , 和 ; 会捕获发生的异常,而 andalso 和 orelse 不会。
.
这意味着如果「,」分隔的两个卫语句,前半部分抛出了异常,后半部分仍然会被求值,整个卫语句仍可能成功;而 andalso 和 orelse 如果前半部分抛出了异常,后半部分就会被跳过
.
在卫语句中,只有 andalso 和 orelse 可以嵌套使用