天天看點

Erlang Guard 和布爾表達式

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 可以嵌套使用