檢視原始碼 運算式

本節列出所有有效的 Erlang 運算式。撰寫 Erlang 程式時,也允許使用巨集和記錄運算式。但是,這些運算式會在編譯期間展開,因此在某種意義上來說並非真正的 Erlang 運算式。巨集和記錄運算式將在單獨的章節中介紹。

運算式求值

除非另有明確說明,否則所有子運算式都會在運算式本身求值之前進行求值。例如,考慮以下運算式:

Expr1 + Expr2

Expr1Expr2 也是運算式,它們會在執行加法運算之前先進行求值 — 以任何順序。

許多運算子只能應用於特定類型的引數。例如,算術運算子只能應用於數字。錯誤類型的引數會導致 badarg 執行階段錯誤。

運算式的最簡單形式是項,也就是 integer/0float/0atom/0string/0list/0map/0tuple/0 之一。傳回值是項本身。

變數

變數是一個運算式。如果變數已繫結到值,則傳回值是該值。未繫結的變數僅允許在模式中使用。

變數以大寫字母或底線 (_) 開頭。變數可以包含字母數字字元、底線和 @

範例

X
Name1
PhoneNumber
Phone_number
_
_Height
name@node

變數使用模式比對繫結到值。Erlang 使用單一指派,也就是說,變數只能繫結一次。

匿名變數 以底線 (_) 表示,並且可以在需要變數但其值可以忽略時使用。

範例

[H|_] = [1,2,3]

以底線 (_) 開頭的變數,例如 _Height,是普通變數,而非匿名變數。但是,編譯器會忽略它們,因為它們不會產生警告。

範例

以下程式碼

member(_, []) ->
    [].

可以重寫為更具可讀性:

member(Elem, []) ->
    [].

這會產生未使用的變數 Elem 的警告。為了避免警告,可以將程式碼重寫為:

member(_Elem, []) ->
    [].

請注意,由於以底線開頭的變數不是匿名的,因此以下範例符合:

{_,_} = {1,2}

但此範例會失敗:

{_N,_N} = {1,2}

變數的範圍是其函數子句。在 ifcasereceive 運算式的分支中繫結的變數,必須在所有分支中都繫結才能在運算式之外擁有值。否則,它們在運算式之外會被視為不安全。

對於 try 運算式,變數範圍是有限的,因此在運算式中繫結的變數在運算式之外始終是不安全的。

模式

模式具有與項相同的結構,但可以包含未繫結的變數。

範例

Name1
[H|T]
{error,Reason}

模式允許在子句標頭、case 運算式receive 運算式match 運算式 中使用。

複合模式運算子

如果 Pattern1Pattern2 是有效的模式,則以下也是有效的模式:

Pattern1 = Pattern2

當與項比對時,Pattern1Pattern2 都會與該項比對。此功能背後的想法是避免重建項。

範例

f({connect,From,To,Number,Options}, To) ->
    Signal = {connect,From,To,Number,Options},
    ...;
f(Signal, To) ->
    ignore.

可以改寫為:

f({connect,_,To,_,_} = Signal, To) ->
    ...;
f(Signal, To) ->
    ignore.

複合模式運算子不表示其運算元會以任何特定順序比對。這表示在 Pattern1 中繫結變數並在 Pattern2 中使用它是非法的,反之亦然。

模式中的字串前置詞

比對字串時,以下是有效的模式:

f("prefix" ++ Str) -> ...

這是等效但較難閱讀的語法糖:

f([$p,$r,$e,$f,$i,$x | Str]) -> ...

模式中的運算式

如果算術運算式滿足以下兩個條件,則可以在模式中使用它:

  • 它只使用數值或位元運算子。
  • 其值可以在編譯時評估為常數。

範例

case {Value, Result} of
    {?THRESHOLD+1, ok} -> ...

比對運算子

以下將 PatternExpr 比對:

Pattern = Expr

如果比對成功,則模式中的任何未繫結變數都會被繫結,並傳回 Expr 的值。

如果依序應用多個比對運算子,它們將從右到左進行評估。

如果比對失敗,則會發生 badmatch 執行階段錯誤。

範例

1> {A, B} = T = {answer, 42}.
{answer,42}
2> A.
answer
3> B.
42
4> T.
{answer,42}
5> {C, D} = [1, 2].
** exception error: no match of right-hand side value [1,2]

由於多個比對運算子是從右到左評估的,這表示:

Pattern1 = Pattern2 = . . . = PatternN = Expression

等同於:

Temporary = Expression,
PatternN = Temporary,
   .
   .
   .,
Pattern2 = Temporary,
Pattern = Temporary

比對運算子和複合模式運算子

注意

這是一個進階章節,其中引用了尚未介紹的主題。首次閱讀時可以安全地跳過它。

= 字元用於表示兩個相似但不同的運算子:比對運算子和複合模式運算子。哪個是根據上下文確定。

複合模式運算子 用於從兩個模式建構複合模式。在任何接受模式的地方都接受複合模式。如果其所有組成的模式都比對,則複合模式比對成功。作為複合模式一部分的模式,使用在相同複合模式的其他子模式中繫結的變數(作為地圖模式中的鍵或二進位模式中的大小)是非法的。

範例

1> fun(#{Key := Value} = #{key := Key}) -> Value end.
* 1:7: variable 'Key' is unbound
2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).
{{1,2},3}
3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>).
{42,43,10795}

在任何允許運算式的地方都允許使用比對運算子。它用於將運算式的值與模式比對。如果依序應用多個比對運算子,它們將從右到左進行評估。

範例

1> M = #{key => key2, key2 => value}.
#{key => key2,key2 => value}
2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
value
3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
value
4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
* 1:12: variable 'Key' is unbound
5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
42

提示 2> 處的運算式首先將變數 M 的值與模式 #{key := Key} 比對,繫結變數 Key。然後,它使用變數 Key 作為鍵,將 M 的值與模式 #{Key := Value} 比對,繫結變數 Value

提示 3> 處的運算式將運算式 (#{key := Key} = M) 與模式 #{Key := Value} 比對。括號內的運算式會先進行評估。也就是說,M#{key := Key} 比對,然後 M 的值與模式 #{Key := Value} 比對。這與 2 中的評估順序相同;因此,括號是多餘的。

在提示 4> 處的運算式中,M 的運算式與括號內的模式比對。由於括號內的建構是模式,因此分隔兩個模式的 = 是複合模式運算子(而非比對運算子)。比對失敗,因為兩個子模式同時比對,因此在與模式 #{Key := Value} 比對時,變數 Key 未繫結。

在提示 5> 處的運算式中,區塊運算式內的運算式會先進行評估,繫結變數 Y 並建立二進位。然後,使用 Y 的值作為區段的大小,將二進位與模式 <<X:Y>> 比對。

函式呼叫

ExprF(Expr1,...,ExprN)
ExprM:ExprF(Expr1,...,ExprN)

在第一種形式的函式呼叫 ExprM:ExprF(Expr1,...,ExprN) 中,ExprMExprF 的每一個都必須是原子或評估為原子的運算式。函式稱為透過使用完整限定函式名稱來呼叫。這通常稱為遠端外部函式呼叫

範例

lists:keyfind(Name, 1, List)

在第二種形式的函式呼叫 ExprF(Expr1,...,ExprN) 中,ExprF 必須是原子或評估為 fun。

如果 ExprF 是原子,則函式稱為透過使用隱含限定函式名稱來呼叫。如果函式 ExprF 是在本機定義的,則會呼叫它。或者,如果 ExprF 是從 M 模組明確匯入的,則會呼叫 M:ExprF(Expr1,...,ExprN)。如果 ExprF 未在本機宣告或明確匯入,則 ExprF 必須是自動匯入的 BIF 的名稱。

範例

handle(Msg, State)
spawn(m, init, [])

範例,其中 ExprF 是 fun

1> Fun1 = fun(X) -> X+1 end,
Fun1(3).
4
2> fun lists:append/2([1,2], [3,4]).
[1,2,3,4]
3>

請注意,在呼叫本機函式時,使用隱含或完整限定函式名稱之間存在差異。後者始終是指模組的最新版本。請參閱編譯和程式碼載入函式評估

與自動匯入的 BIF 衝突的本機函式名稱

如果本機函式的名稱與自動匯入的 BIF 的名稱相同,則語意是隱含限定的函式呼叫會定向到本機定義的函式,而不是 BIF。為了避免混淆,有一個編譯器指示詞可用,-compile({no_auto_import,[F/A]}),它會使 BIF 不會自動匯入。在某些情況下,此類編譯指示詞是強制性的。

變更

在 Erlang/OTP R14A (ERTS 版本 5.8) 之前,對與自動匯入的 BIF 名稱相同的函式進行隱含限定的函式呼叫,始終會導致呼叫 BIF。在新版本的編譯器中,會改為呼叫本機函式。這是為了避免將來新增到自動匯入的 BIF 集合,不會靜默地變更舊程式碼的行為。

然而,為了避免舊的(R14 之前的)程式碼在以 Erlang/OTP 版本 R14A 或更新版本編譯時改變其行為,以下限制適用:如果您覆寫了一個在 OTP 版本 R14A 之前(ERTS 版本 5.8)自動匯入的 BIF 的名稱,並且您的程式碼中對該函式有隱式限定的呼叫,您需要使用編譯器指令顯式移除自動匯入,或將該呼叫替換為完整限定的函式呼叫。否則,您會收到編譯錯誤。請參閱以下範例。

-export([length/1,f/1]).

-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported

length([]) ->
    0;
length([H|T]) ->
    1 + length(T). %% Calls the local function length/1

f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
                                  %% which is allowed in guards
    long.

相同的邏輯適用於從其他模組顯式匯入的函式,以及本地定義的函式。不允許同時從另一個模組匯入函式,又同時在模組中宣告該函式。

-export([f/1]).

-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported

-import(mod,[length/1]).

f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
                                   %% which is allowed in guards

    erlang:length(X);              %% Explicit call to erlang:length in body

f(X) ->
    length(X).                     %% mod:length/1 is called

對於在 Erlang/OTP R14A 及之後版本新增的自動匯入 BIF,總是允許使用本地函式或顯式匯入來覆寫其名稱。但是,如果未使用 -compile({no_auto_import,[F/A]) 指令,則只要在模組中使用隱式限定的函式名稱呼叫該函式,編譯器就會發出警告。

If (如果)

if
    GuardSeq1 ->
        Body1;
    ...;
    GuardSeqN ->
        BodyN
end

if 表達式的分支會依序掃描,直到找到一個評估為 true 的 guard 序列 GuardSeq。然後,評估對應的 Body(以 , 分隔的表達式序列)。

Body 的回傳值是 if 表達式的回傳值。

如果沒有 guard 序列評估為 true,則會發生 if_clause 執行階段錯誤。如有必要,可以在最後一個分支使用 guard 表達式 true,因為該 guard 序列始終為 true。

範例

is_greater_than(X, Y) ->
    if
        X > Y ->
            true;
        true -> % works as an 'else' branch
            false
    end

Case (情況)

case Expr of
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
end

評估表達式 Expr,並將模式 Pattern 依序與結果比對。如果比對成功,且可選的 guard 序列 GuardSeq 為 true,則評估對應的 Body

Body 的回傳值是 case 表達式的回傳值。

如果沒有符合的模式且 guard 序列為 true,則會發生 case_clause 執行階段錯誤。

範例

is_valid_signal(Signal) ->
    case Signal of
        {signal, _What, _From, _To} ->
            true;
        {signal, _What, _To} ->
            true;
        _Else ->
            false
    end.

Maybe (可能)

變更

maybe 功能 在 Erlang/OTP 25 中引入。從 Erlang/OTP 27 開始,預設啟用此功能。

maybe
    Expr1,
    ...,
    ExprN
end

maybe 區塊中的表達式會依序評估。如果所有表達式都成功評估,則 maybe 區塊的回傳值是 ExprN。但是,執行可以透過條件式比對表達式短路。

Expr1 ?= Expr2

?= 稱為條件式比對運算子。只允許在 maybe 區塊的頂層使用。它會將模式 Expr1Expr2 比對。如果比對成功,模式中任何未綁定的變數都會被綁定。如果該表達式是 maybe 區塊中的最後一個表達式,它也會回傳 Expr2 的值。如果比對不成功,則會跳過 maybe 區塊中的其餘表達式,並且 maybe 區塊的回傳值為 Expr2

maybe 區塊中綁定的變數都不得在區塊後面的程式碼中使用。

以下是一個範例

maybe
    {ok, A} ?= a(),
    true = A >= 0,
    {ok, B} ?= b(),
    A + B
end

首先假設 a() 回傳 {ok,42}b() 回傳 {ok,58}。使用這些回傳值,所有的比對運算子都會成功,並且 maybe 區塊的回傳值是 A + B,等於 42 + 58 = 100

現在假設 a() 回傳 error{ok, A} ?= a() 中的條件式比對運算子無法比對成功,並且 maybe 區塊的回傳值是無法比對成功的表達式的值,也就是 error。類似地,如果 b() 回傳 wrong,則 maybe 區塊的回傳值是 wrong

最後,假設 a() 回傳 {ok,-1}。由於 true = A >= 0 使用比對運算子 =,因此當表達式無法與模式比對時,會發生 {badmatch,false} 執行階段錯誤。

該範例可以使用巢狀的 case 表達式,以較不簡潔的方式編寫。

case a() of
    {ok, A} ->
        true = A >= 0,
        case b() of
            {ok, B} ->
                A + B;
            Other1 ->
                Other1
        end;
    Other2 ->
        Other2
end

maybe 區塊可以使用 else 子句來擴充。

maybe
    Expr1,
    ...,
    ExprN
else
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
end

如果條件式比對運算子失敗,則會將失敗的表達式與 elseend 關鍵字之間的所有子句中的模式比對。如果比對成功,且可選的 guard 序列 GuardSeq 為 true,則評估對應的 Body。從 body 回傳的值是 maybe 區塊的回傳值。

如果沒有符合的模式且 guard 序列為 true,則會發生 else_clause 執行階段錯誤。

maybe 區塊中綁定的變數都不得在 else 子句中使用。else 子句中綁定的變數都不得在 maybe 區塊後面的程式碼中使用。

以下是使用 else 子句擴充的先前範例

maybe
    {ok, A} ?= a(),
    true = A >= 0,
    {ok, B} ?= b(),
    A + B
else
    error -> error;
    wrong -> error
end

else 子句將條件式比對運算子的失敗值轉換為 error 值。如果失敗值不是已識別的值之一,則會發生 else_clause 執行階段錯誤。

Send (傳送)

Expr1 ! Expr2

Expr2 的值作為訊息傳送至 Expr1 指定的程序。Expr2 的值也是表達式的回傳值。

Expr1 必須評估為 pid、別名 (參考)、port、已註冊的名稱 (atom),或 tuple {Name,Node}Name 是一個 atom,而 Node 是一個節點名稱,也是一個 atom。

  • 如果 Expr1 評估為名稱,但此名稱未註冊,則會發生 badarg 執行階段錯誤。
  • 傳送訊息給參考永遠不會失敗,即使該參考不再是 (或從未是) 別名。
  • 傳送訊息給 pid 永遠不會失敗,即使該 pid 識別一個不存在的程序。
  • 分散式訊息傳送,也就是說,如果 Expr1 評估為 tuple {Name,Node} (或位於另一個節點的 pid),也永遠不會失敗。

Receive (接收)

receive
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
end

提取程序訊息佇列中存在的已接收訊息。訊息佇列中的第一個訊息會依序從上到下與模式比對。如果未找到任何比對,則會針對佇列中的第二個訊息重複比對序列,依此類推。訊息會以接收的順序 排隊。如果比對成功,也就是 Pattern 比對成功,且可選的 guard 序列 GuardSeq 為 true,則該訊息會從訊息佇列中移除,並評估對應的 Body。訊息佇列中的所有其他訊息保持不變。

Body 的回傳值是 receive 表達式的回傳值。

receive 永遠不會失敗。執行會暫停,可能無限期地暫停,直到收到一個符合模式且 guard 序列為 true 的訊息為止。

範例

wait_for_onhook() ->
    receive
        onhook ->
            disconnect(),
            idle();
        {connect, B} ->
            B ! {busy, self()},
            wait_for_onhook()
    end.

receive 表達式可以使用逾時時間來擴充。

receive
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
after
    ExprT ->
        BodyT
end

receive...after 的運作方式與 receive 完全相同,只是如果在 ExprT 毫秒內沒有收到符合的訊息,則會評估 BodyT。然後,BodyT 的回傳值會變成 receive...after 表達式的回傳值。ExprT 的評估結果必須為整數或 atom infinity。允許的整數範圍從 0 到 4294967295,也就是說,最長的逾時時間幾乎是 50 天。如果值為零,則當訊息佇列中沒有符合的訊息時,會立即發生逾時。

atom infinity 將使程序無限期地等待符合的訊息。這與不使用逾時時間相同。對於在執行階段計算的逾時時間值很有用。

範例

wait_for_onhook() ->
    receive
        onhook ->
            disconnect(),
            idle();
        {connect, B} ->
            B ! {busy, self()},
            wait_for_onhook()
    after
        60000 ->
            disconnect(),
            error()
    end.

可以使用沒有分支的 receive...after 表達式。

receive
after
    ExprT ->
        BodyT
end

此結構不會消耗任何訊息,只會將程序中的執行暫停 ExprT 毫秒。這可以用於實現簡單的計時器。

範例

timer() ->
    spawn(m, timer, [self()]).

timer(Pid) ->
    receive
    after
        5000 ->
            Pid ! timeout
    end.

有關 Erlang 中計時器的更多資訊,請參閱 計時器 章節的 Erlang 中的時間和時間校正 ERTS 使用者指南。

Term Comparisons (項比較)

Expr1 op Expr2
op (運算子)Description (描述)
==Equal to (等於)
/=Not equal to (不等於)
=<Less than or equal to (小於或等於)
<Less than (小於)
>=Greater than or equal to (大於或等於)
>Greater than (大於)
=:=Term equivalence (項相等)
=/=Term non-equivalence (項不相等)

表:項比較運算子。

引數可以是不同的資料類型。定義了以下順序

number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string

先前表達式中的 nil 代表空列表 ([]),它被視為與 list/0 不同的類型。這就是為什麼 nil < list

列表會逐個元素進行比較。tuple 會按大小排序,兩個大小相同的 tuple 會逐個元素進行比較。

位元字串會逐位元進行比較。如果一個位元字串是另一個位元字串的前綴,則較短的位元字串會被認為較小。

map 會按大小排序,兩個大小相同的 map 會按鍵的遞增項順序,然後按鍵順序中的值進行比較。在 map 中,鍵的順序整數類型會被視為小於浮點數類型。

Atom 會使用其字串值逐個程式碼點進行比較。

當比較整數和浮點數時,除非運算符是 =:==/= 之一,否則精確度較低的項會被轉換為另一項的類型。在浮點數的所有有效數字都位於小數點左側之前,浮點數的精確度高於整數。當浮點數大於/小於 +/-9007199254740992.0 時,就會發生這種情況。轉換策略會根據浮點數的大小而改變,因為否則比較大型浮點數和整數將會失去其遞移性。

項等價運算符 =:==/= 會返回兩個項是否無法區分。雖然其他運算符會認為相同的數字相等,即使它們的類型不同(1 == 1.0 為 true),但項等價運算符會返回是否存在區分參數的方法。

例如,雖然項 00.0 代表相同的數字,但我們可以透過使用 is_integer/1 函數來區分它們。因此,=:==/= 會認為它們不同。

此外,項 0.0-0.0 也代表相同的數字,但當透過 float_to_list/1 轉換為字串形式時,它們會產生不同的結果:給定前者時,它會返回一個不帶正負號的字串,而給定後者時,它會返回一個帶有正負號的字串。因此,=:==/= 會認為它們不同。

當將項作為不透明值進行推理時,例如在關聯容器或記憶化函數中,項等價運算符會很有用,因為使用等於運算符 (==) 可能會因為混合使用不同類型的數字而產生不正確的結果。

項比較運算符會返回表達式的布林值,truefalse

範例

1> 1 == 1.0.
true
2> 1 =:= 1.0.
false
3> 0 =:= 0.0.
false
4> 0.0 =:= -0.0.
false
5> 0.0 =:= +0.0.
true
6> 1 > a.
false
7> #{c => 3} > #{a => 1, b => 2}.
false
8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}.
true
9> <<2:2>> < <<128>>.
true
10> <<3:2>> < <<128>>.
false

注意

在 OTP 27 之前,項等價運算符認為 0.0-0.0 是相同的項。

這在 OTP 27 中已變更,但舊程式碼可能預期它們會被視為相同。為了幫助使用者捕捉升級可能產生的錯誤,當 0.0 在模式比對或在項等價測試中使用時,編譯器會發出警告。

如果您需要特別比對 0.0,則可以改為寫 +0.0 來抑制警告,這會產生相同的項,但會讓編譯器將比對解讀為有意為之。

算術表達式

op Expr
Expr1 op Expr2
運算符Description (描述)參數類型
+一元 +數字
-負號(一元 -)數字
+加法數字
-減法數字
*乘法數字
/浮點數除法數字
bnot一元位元 NOT整數
div整數除法整數
remX/Y 的整數餘數整數
band位元 AND整數
bor位元 OR整數
bxor位元 XOR整數
bsl位元左移整數
bsr算術位元右移整數

表格:算術運算符。

範例

1> +1.
1
2> -1.
-1
3> 1+1.
2
4> 4/2.
2.0
5> 5 div 2.
2
6> 5 rem 2.
1
7> 2#10 band 2#01.
0
8> 2#10 bor 2#01.
3
9> a + 10.
** exception error: an error occurred when evaluating an arithmetic expression
     in operator  +/2
        called as a + 10
10> 1 bsl (1 bsl 64).
** exception error: a system limit has been reached
     in operator  bsl/2
        called as 1 bsl 18446744073709551616

布林表達式

op Expr
Expr1 op Expr2
運算符Description (描述)
not一元邏輯 NOT
and邏輯 AND
or邏輯 OR
xor邏輯 XOR

表格:邏輯運算符。

範例

1> not true.
false
2> true and false.
false
3> true xor false.
true
4> true or garbage.
** exception error: bad argument
     in operator  or/2
        called as true or garbage

短路表達式

Expr1 orelse Expr2
Expr1 andalso Expr2

只有在必要時才會評估 Expr2。也就是說,只有在以下情況下才會評估 Expr2

  • orelse 表達式中,Expr1 評估為 false

or

  • andalso 表達式中,Expr1 評估為 true

返回 Expr1 的值(即 truefalse)或 Expr2 的值(如果評估 Expr2)。

範例 1

case A >= -1.0 andalso math:sqrt(A+1) > B of

即使 A 小於 -1.0,這也能正常運作,因為在這種情況下,永遠不會評估 math:sqrt/1

範例 2

OnlyOne = is_atom(L) orelse
         (is_list(L) andalso length(L) == 1),

不需要將 Expr2 評估為布林值。因此,andalsoorelse 是尾遞迴的。

範例 3(尾遞迴函數)

all(Pred, [Hd|Tail]) ->
    Pred(Hd) andalso all(Pred, Tail);
all(_, []) ->
    true.

變更

在 Erlang/OTP R13A 之前,需要將 Expr2 評估為布林值,因此 andalsoorelse不是尾遞迴的。

列表操作

Expr1 ++ Expr2
Expr1 -- Expr2

列表串聯運算符 ++ 會將第二個參數附加到第一個參數,並返回結果列表。

列表減法運算符 -- 會產生一個列表,該列表是第一個參數的副本。程序如下:對於第二個參數中的每個元素,會移除此元素(如果有的話)的第一次出現。

範例

1> [1,2,3] ++ [4,5].
[1,2,3,4,5]
2> [1,2,3,2,1,2] -- [2,1,2].
[3,1,2]

映射表達式

建立映射

建立新映射是透過將表達式 K 與另一個表達式 V 關聯來完成的

#{K => V}

新映射可以在建構時透過列出每個關聯來包含多個關聯

#{K1 => V1, ..., Kn => Vn}

空映射是透過不將任何項彼此關聯來建構的

#{}

映射中的所有鍵和值都是項。首先會評估任何表達式,然後將結果項分別用作

鍵和值以 => 箭頭分隔,關聯以逗號 (,) 分隔。

範例

M0 = #{},                 % empty map
M1 = #{a => <<"hello">>}, % single association with literals
M2 = #{1 => 2, b => b},   % multiple associations with literals
M3 = #{k => {A,B}},       % single association with variables
M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

在此,AB 是任何表達式,而 M0M4 是結果映射項。

如果宣告了兩個相符的鍵,則後面的鍵優先。

範例

1> #{1 => a, 1 => b}.
#{1 => b }
2> #{1.0 => a, 1 => b}.
#{1 => b, 1.0 => a}

評估建構鍵(及其關聯值)的表達式的順序未定義。建構中鍵值對的語法順序並不重要,除非在最近提到的兩個相符鍵的情況下。

更新映射

更新映射的語法與建構映射類似。

定義要更新的映射的表達式放在定義要更新的鍵及其各自值的表達式之前

M#{K => V}

此處 M 是映射類型的項,而 KV 是任何表達式。

如果鍵 K 與映射中任何現有的鍵不符,則會建立從鍵 K 到值 V 的新關聯。

如果鍵 K 與映射 M 中的現有鍵相符,則其關聯值會被新值 V 取代。在這兩種情況下,評估後的映射表達式都會返回一個新映射。

如果 M 不是映射類型,則會引發 badmap 類型的例外狀況。

若要僅更新現有值,請使用以下語法

M#{K := V}

此處 M 是映射類型的項,V 是一個表達式,而 K 是一個會評估為 M 中現有鍵的表達式。

如果鍵 K 與映射 M 中的任何現有鍵不符,則會在執行階段引發 badkey 類型的例外狀況。如果映射 M 中存在相符的鍵 K,則其關聯值會被新值 V 取代,並且評估後的映射表達式會返回一個新映射。

如果 M 不是映射類型,則會引發 badmap 類型的例外狀況。

範例

M0 = #{},
M1 = M0#{a => 0},
M2 = M1#{a => 1, b => 2},
M3 = M2#{"function" => fun() -> f() end},
M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

此處 M0 是任何映射。由此可見,M1M4 也都是映射。

更多範例

1> M = #{1 => a}.
#{1 => a }
2> M#{1.0 => b}.
#{1 => a, 1.0 => b}.
3> M#{1 := b}.
#{1 => b}
4> M#{1.0 := b}.
** exception error: bad argument

如同在建構中,評估鍵和值表達式的順序未定義。更新中鍵值對的語法順序並不重要,除非在兩個鍵相符的情況下。在這種情況下,會使用後面的值。

模式中的映射

從映射比對鍵值關聯的方式如下

#{K := V} = M

此處 M 是任何映射。鍵 K 必須是 保護表達式,其中所有變數都已繫結。V 可以是任何具有已繫結或未繫結變數的模式。

如果變數 V 未繫結,則它會繫結到與鍵 K 關聯的值,該值必須存在於映射 M 中。如果變數 V 已繫結,則它必須與 M 中與 K 關聯的值相符。

變更

在 Erlang/OTP 23 之前,定義鍵 K 的表達式僅限於單一變數或常值。

範例

1> M = #{"tuple" => {1,2}}.
#{"tuple" => {1,2}}
2> #{"tuple" := {1,B}} = M.
#{"tuple" => {1,2}}
3> B.
2.

這會將變數 B 繫結到整數 2

同樣地,可以比對映射中的多個值

#{K1 := V1, ..., Kn := Vn} = M

此處的鍵 K1Kn 是具有常值或已繫結變數的任何表達式。如果所有鍵表達式都評估成功,且所有鍵都存在於映射 M 中,則 V1 .. Vn 中的所有變數都會與其各自鍵的關聯值比對。

如果不滿足比對條件,則比對失敗。

請注意,當比對映射時,只允許使用 := 運算符(而不是 =>)作為關聯的分隔符號。

宣告比對中鍵的順序並不重要。

比對中允許重複的鍵,並且比對與鍵關聯的每個模式

#{K := V1, K := V2} = M

當用作模式時,空映射常值 (#{}) 會比對任何映射

#{} = Expr

如果表達式 Expr 是映射類型,則此表達式會比對,否則會因為 badmatch 例外狀況而失敗。

此處要擷取的鍵是從表達式建構的

#{{tag,length(List)} := V} = Map

List 必須是一個已繫結的變數。

比對語法

允許在函數標頭中比對作為鍵的常值

%% only start if not_started
handle_call(start, From, #{state := not_started} = S) ->
...
    {reply, ok, S#{state := start}};

%% only change if started
handle_call(change, From, #{state := start} = S) ->
...
    {reply, ok, S#{state := changed}};

Guard 中的 Map

只要所有子表達式都是有效的 guard 表達式,map 就可以在 guard 中使用。

以下 guard BIF 可以處理 map

位元語法表達式

位元語法操作位元字串。位元字串是由最高有效位到最低有效位排序的位元序列。

<<>>  % The empty bit string, zero length
<<E1>>
<<E1,...,En>>

每個元素 Ei 指定位元字串的一個區段。這些區段從位元字串的最高有效位到最低有效位,從左到右排序。

每個區段規格 Ei 都是一個值,後面可選擇加上一個大小表達式和一個可選的類型規範列表

Ei = Value |
     Value:Size |
     Value/TypeSpecifierList |
     Value:Size/TypeSpecifierList

當用於位元字串建構時,Value 是一個表達式,其求值結果為整數、浮點數或位元字串。如果表達式不是單個字面值或變數,則必須用括號括起來。

當用於位元字串匹配時,Value 必須是一個變數,或一個整數、浮點數或字串。

請注意,例如,使用字串字面值,如 <<"abc">><<$a,$b,$c>> 的語法糖。

當用於位元字串建構時,Size 是一個表達式,其求值結果為整數。

當用於位元字串匹配時,Size 必須是一個求值結果為整數的guard 表達式。guard 表達式中的所有變數必須已經綁定。

變更

在 Erlang/OTP 23 之前,Size 被限制為整數或綁定到整數的變數。

Size 的值指定區段的大小(單位見下文)。預設值取決於類型(見下文)

  • 對於 integer,預設值為 8。
  • 對於 float,預設值為 64。
  • 對於 binarybitstring,預設值為整個二進位或位元字串。

在匹配中,二進位或位元字串區段的預設值僅對最後一個元素有效。匹配中的所有其他位元字串或二進位元素都必須具有大小規範。

二進位

長度為 8 位元倍數的位元字串稱為二進位,它是最常見和最有用的位元字串類型。

二進位在記憶體中具有規範表示法。以下是一個位元組序列,其中每個位元組的值都是其序列號

<<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

位元字串是二進位的較晚通用化,因此許多關於二進位的文字和資訊也適用於位元字串。

範例

1> <<A/binary, B/binary>> = <<"abcde">>.
* 1:3: a binary field without size is only allowed at the end of a binary pattern
2> <<A:3/binary, B/binary>> = <<"abcde">>.
<<"abcde">>
3> A.
<<"abc">>
4> B.
<<"de">>

對於 utf8utf16utf32 類型,不得指定 Size。區段的大小由類型和值本身隱式決定。

TypeSpecifierList 是類型規範列表,可以以任何順序排列,並以連字符號 (-) 分隔。任何省略的類型規範都使用預設值。

  • Type= integer | float | binary | bytes | bitstring | bits | utf8 | utf16 | utf32 - 預設值為 integerbytesbinary 的簡寫,而 bitsbitstring 的簡寫。有關 utf 類型的詳細資訊,請參閱下文。

  • Signedness= signed | unsigned - 僅在匹配且類型為 integer 時才重要。預設值為 unsigned

  • Endianness= big | little | native - 指定位元組級(八位元級)的 endianness(位元組順序)。原生 endianness 表示 endianness 在載入時解析為大端或小端,具體取決於執行 Erlang 機器 CPU 的原生設定。endianness 僅在 Typeintegerutf16utf32float 時才重要。預設值為 big

    <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
  • Unit= unit:IntegerLiteral - 允許的範圍是 1 到 256。對於 integerfloatbitstring,預設值為 1,而對於 binary,預設值為 8。對於 bitstringbitsbytes 類型,不允許指定與預設值不同的單位值。對於 utf8utf16utf32 類型,不得指定任何單位規範。

整數區段

Size 的值乘以單位即得出區段大小,單位為位元。

建構位元字串時,如果整數區段的大小 N 太小而無法容納給定的整數,則會無訊息地捨棄整數的最高有效位,只有 N 個最低有效位會放入位元字串中。例如,<<16#ff:4>> 將產生位元字串 <<15:4>>

浮點數區段

Size 的值乘以單位即得出區段大小,單位為位元。浮點數區段的大小(位元)必須為 16、32 或 64。

建構位元字串時,如果浮點數區段的大小太小而無法容納給定的浮點數值的表示法,則會引發例外狀況。

當匹配位元字串時,如果區段的位元不包含有限浮點數值的表示法,則浮點數區段的匹配會失敗。

二進位區段

在本節中,「二進位區段」一詞是指任何區段類型,包括 binarybitstringbytesbits

另請參閱關於二進位的段落。

當建構二進位且未為二進位區段指定大小時,整個二進位值會插入到正在建構的二進位中。但是,正在插入的二進位的位元大小必須能被區段的單位值整除,否則會引發例外狀況。

例如,以下範例都會成功

1> <<(<<"abc">>)/bitstring>>.
<<"abc">>
2> <<(<<"abc">>)/binary-unit:1>>.
<<"abc">>
3> <<(<<"abc">>)/binary>>.
<<"abc">>

前兩個範例的區段單位值為 1,而第三個區段的單位值為 8。

嘗試將大小為 1 的位元字串插入到單位為 8(binary 的預設單位)的二進位區段會失敗,如本範例所示

1> <<(<<1:1>>)/binary>>.
** exception error: bad argument

若要讓建構成功,區段的單位值必須為 1

2> <<(<<1:1>>)/bitstring>>.
<<1:1>>
3> <<(<<1:1>>)/binary-unit:1>>.
<<1:1>>

同樣地,當匹配未指定大小的二進位區段時,當且僅當剩餘二進位的位元大小能被單位值整除時,匹配才會成功

1> <<_/binary-unit:16>> = <<"">>.
<<>>
2> <<_/binary-unit:16>> = <<"a">>.
** exception error: no match of right hand side value <<"a">>
3> <<_/binary-unit:16>> = <<"ab">>.
<<"ab">>
4> <<_/binary-unit:16>> = <<"abc">>.
** exception error: no match of right hand side value <<"abc">>
5> <<_/binary-unit:16>> = <<"abcd">>.
<<"abcd">>

當為二進位區段明確指定大小時,區段大小(位元)為 Size 的值乘以預設或明確指定的單位值。

建構二進位時,插入到建構二進位的二進位大小必須至少與二進位區段的大小相同。

範例

1> <<(<<"abc">>):2/binary>>.
<<"ab">>
2> <<(<<"a">>):2/binary>>.
** exception error: construction of binary failed
        *** segment 1 of type 'binary': the value <<"a">> is shorter than the size of the segment

Unicode 區段

utf8utf16utf32 類型分別指定Unicode 轉換格式 UTF-8UTF-16UTF-32 的編碼/解碼。

建構 utf 類型的區段時,Value 必須是介於 016#D7FF16#E00016#10FFFF 之間的整數。如果 Value 超出允許的範圍,則建構會失敗,並引發 badarg 例外狀況。編碼值的大小如下:

  • 對於 utf8Value 編碼為 1-4 個位元組。
  • 對於 utf16Value 編碼為 2 或 4 個位元組。
  • 對於 utf32Value 編碼為 4 個位元組。

建構時,可以給出字串字面值,後面跟著其中一個 UTF 類型,例如:<<"abc"/utf8>>,它是 <<$a/utf8,$b/utf8,$c/utf8>> 的語法糖。

成功匹配 utf 類型的區段,會產生介於 016#D7FF16#E00016#10FFFF 之間的整數。如果傳回值超出這些範圍,則匹配會失敗。

如果位元字串在匹配位置包含有效的 UTF-8 序列,則 utf8 類型的區段會匹配位元字串中的 1-4 個位元組。(請參閱 RFC-3629 或 Unicode 標準。)

類型為 utf16 的區段可以匹配位元字串中的 2 或 4 個位元組。如果匹配位置的位元字串不包含 Unicode 碼點的合法 UTF-16 編碼,則匹配失敗。(請參閱 RFC-2781 或 Unicode 標準。)

類型為 utf32 的區段可以像 integer 區段匹配 32 位元一樣,匹配位元字串中的 4 個位元組。如果產生的整數超出先前所述的合法範圍,則匹配失敗。

範例

1> Bin1 = <<1,17,42>>.
<<1,17,42>>
2> Bin2 = <<"abc">>.
<<97,98,99>>

3> Bin3 = <<1,17,42:16>>.
<<1,17,0,42>>
4> <<A,B,C:16>> = <<1,17,42:16>>.
<<1,17,0,42>>
5> C.
42
6> <<D:16,E,F>> = <<1,17,42:16>>.
<<1,17,0,42>>
7> D.
273
8> F.
42
9> <<G,H/binary>> = <<1,17,42:16>>.
<<1,17,0,42>>
10> H.
<<17,0,42>>
11> <<G,J/bitstring>> = <<1,17,42:12>>.
<<1,17,2,10:4>>
12> J.
<<17,2,10:4>>

13> <<1024/utf8>>.
<<208,128>>

14> <<1:1,0:7>>.
<<128>>
15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>.
<<35,1:4>>

請注意,位元字串模式不能巢狀。

另請注意,"B=<<1>>" 會被解讀為 "B =< <1>>",這是一個語法錯誤。正確的方法是在 = 後面寫一個空格:"B = <<1>>"。

更多範例請參閱程式設計範例

Fun 運算式

fun
    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
              Body1;
    ...;
    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
              BodyK
end

Fun 運算式以關鍵字 fun 開始,並以關鍵字 end 結束。它們之間是一個函式宣告,類似於常規函式宣告,只是函式名稱是可選的,並且如果有的話,它應該是一個變數。

fun 頭部的變數會遮蔽函式名稱,並且兩者都會遮蔽 fun 運算式周圍函式子句中的變數。在 fun 主體中綁定的變數是 fun 主體的局部變數。

運算式的回傳值是產生的 fun。

範例

1> Fun1 = fun (X) -> X+1 end.
#Fun<erl_eval.6.39074546>
2> Fun1(2).
3
3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
#Fun<erl_eval.6.39074546>
4> Fun2(7).
gt
5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
#Fun<erl_eval.6.39074546>
6> Fun3(4).
24

也允許以下 fun 運算式

fun Name/Arity
fun Module:Name/Arity

Name/Arity 中,Name 是一個原子,而 Arity 是一個整數。Name/Arity 必須指定一個現有的本地函式。該運算式是以下語法的簡寫

fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

Module:Name/Arity 中,ModuleName 是原子,而 Arity 是一個整數。ModuleNameArity 也可以是變數。以這種方式定義的 fun 會參照模組 Module最新版本中,arity 為 Arity 的函式 Name。以這種方式定義的 fun 不依賴於定義它的模組程式碼。

變更

在 Erlang/OTP R15 之前,不允許 ModuleNameArity 為變數。

更多範例請參閱程式設計範例

Catch 和 Throw

catch Expr

傳回 Expr 的值,除非在評估期間引發例外。在這種情況下,會捕獲例外。回傳值取決於例外的類別

  • error (執行階段錯誤或程式碼呼叫 error(Term)) - 傳回 {'EXIT',{Reason,Stack}}

  • exit (程式碼呼叫 exit(Term)) - 傳回 {'EXIT',Term}

  • throw (程式碼呼叫 throw(Term)):傳回 Term

Reason 取決於發生的錯誤類型,而 Stack 是最近函式呼叫的堆疊,請參閱結束原因

範例

1> catch 1+2.
3
2> catch 1+a.
{'EXIT',{badarith,[...]}}

BIF throw(Any) 可用於從函式進行非本地回傳。它必須在 catch 內進行評估,而 catch 會回傳值 Any

範例

3> catch throw(hello).
hello

如果 throw/1 未在 catch 內進行評估,則會發生 nocatch 執行階段錯誤。

變更

在 Erlang/OTP 24 之前,catch 運算子的優先順序最低,因此在將其與 match 運算子組合時,必須新增括號

1> A = (catch 42).
42
2> A.
42

從 Erlang/OTP 24 開始,可以省略括號

1> A = catch 42.
42
2> A.
42

Try

try Exprs
catch
    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
        ExceptionBody1;
    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
        ExceptionBodyN
end

這是對 catch 的增強。它提供了以下可能性

  • 區分不同的例外類別。
  • 選擇僅處理所需的例外。
  • 將其他例外傳遞給封閉的 trycatch,或傳遞給預設的錯誤處理。

請注意,雖然關鍵字 catch 用於 try 運算式中,但 try 運算式中沒有 catch 運算式。

它會回傳 Exprs(運算式序列 Expr1, ..., ExprN)的值,除非在評估期間發生例外。在這種情況下,會捕獲例外,並且將具有正確例外類別 Class 的模式 ExceptionPattern 依序與捕獲的例外進行匹配。如果匹配成功且可選的保護序列 ExceptionGuardSeq 為真,則會評估對應的 ExceptionBody 以成為回傳值。

如果指定了 Stacktrace,則它必須是變數的名稱(而不是模式)。當對應的 ExceptionPattern 匹配時,堆疊追蹤會綁定到該變數。

如果在評估 Exprs 期間發生例外,但沒有與正確 Class 匹配且保護序列為真的 ExceptionPattern,則會像 Exprs 未封裝在 try 運算式中一樣傳遞例外。

如果在評估 ExceptionBody 期間發生例外,則不會捕獲該例外。

允許省略 ClassStacktrace。省略的 Classthrow 的簡寫

try Exprs
catch
    ExceptionPattern1 [when ExceptionGuardSeq1] ->
        ExceptionBody1;
    ExceptionPatternN [when ExceptionGuardSeqN] ->
        ExceptionBodyN
end

try 運算式可以有 of 區段

try Exprs of
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
catch
    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
        ExceptionBody1;
    ...;
    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
        ExceptionBodyN
end

如果 Exprs 的評估在沒有例外的情況下成功,則模式 Pattern 會依序與結果進行匹配,與 case 運算式的方式相同,只是如果匹配失敗,則會發生 try_clause 執行階段錯誤,而不是 case_clause

只有在評估 Exprs 期間發生的例外才能由 catch 區段捕獲。在 Body 中或由於匹配失敗而發生的例外不會被捕獲。

try 運算式也可以使用 after 區段進行擴充,該區段旨在用於具有副作用的清理

try Exprs of
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
catch
    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
        ExceptionBody1;
    ...;
    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
        ExceptionBodyN
after
    AfterBody
end

無論是 Body 還是 ExceptionBody,都會在它們之後評估 AfterBodyAfterBody 的評估值會遺失;帶有 after 區段的 try 運算式的回傳值與沒有 after 區段的回傳值相同。

即使在評估 BodyExceptionBody 期間發生例外,也會評估 AfterBody。在這種情況下,會在評估 AfterBody 之後傳遞例外,因此,帶有 after 區段的 try 運算式中的例外與沒有 after 區段的例外相同。

如果評估 AfterBody 本身期間發生例外,則不會捕獲該例外。因此,如果 AfterBody 是在 ExprsBodyExceptionBody 中發生例外後進行評估,則該例外會遺失,並被 AfterBody 中的例外遮蔽。

ofcatchafter 區段都是可選的,只要至少有一個 catchafter 區段即可。因此,以下是有效的 try 運算式

try Exprs of
    Pattern when GuardSeq ->
        Body
after
    AfterBody
end

try Exprs
catch
    ExpressionPattern ->
        ExpressionBody
after
    AfterBody
end

try Exprs after AfterBody end

以下是使用 after 的範例。即使在 file:read/2binary_to_term/1 中發生例外,這也會關閉檔案。這些例外與沒有 try...after...end 運算式的例外相同

termize_file(Name) ->
    {ok,F} = file:open(Name, [read,binary]),
    try
        {ok,Bin} = file:read(F, 1024*1024),
        binary_to_term(Bin)
    after
        file:close(F)
    end.

以下是使用 try 來模擬 catch Expr 的範例

try Expr
catch
    throw:Term -> Term;
    exit:Reason -> {'EXIT',Reason};
    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
end

在這些運算式的各個部分中綁定的變數具有不同的範圍。剛在 try 關鍵字之後綁定的變數為

  • of 區段中綁定
  • catchafter 區段中,以及在整個結構之後都不安全

of 區段中綁定的變數為

  • catch 區段中未綁定
  • after 區段中,以及在整個結構之後都不安全

catch 區段中綁定的變數在 after 區段中,以及在整個結構之後都不安全。

after 區段中綁定的變數在整個結構之後都不安全。

括號運算式

(Expr)

括號運算式對於覆寫運算子優先順序很有用,例如在算術運算式中

1> 1 + 2 * 3.
7
2> (1 + 2) * 3.
9

區塊運算式

begin
   Expr1,
   ...,
   ExprN
end

區塊運算式提供了一種對一連串運算式進行分組的方式,類似於子句主體。回傳值是最後一個運算式 ExprN 的值。

Comprehensions

Comprehensions 提供了一種簡潔的符號,用於迭代一個或多個項並建構新項。Comprehensions 有三種不同的形式,具體取決於它們建構的項的類型。

List comprehensions 建構清單。它們具有以下語法

[Expr || Qualifier1, . . ., QualifierN]

在這裡,Expr 是任意運算式,每個 Qualifier 都是產生器篩選器

位元字串 comprehensions 建構位元字串或二進位檔。它們具有以下語法

<< BitStringExpr || Qualifier1, . . ., QualifierN >>

BitStringExpr 是一個評估為位元字串的運算式。如果 BitStringExpr 是函式呼叫,則它必須用括號括起來。每個 Qualifier 都是產生器篩選器

Map comprehensions 建構映射。它們具有以下語法

#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

在這裡,KeyExprValueExpr 是任意運算式,每個 Qualifier 都是產生器篩選器

變更

Map comprehensions 和映射產生器是在 Erlang/OTP 26 中引入的。

有三種產生器。

清單產生器具有以下語法

Pattern <- ListExpr

其中 ListExpr 是一個評估為項清單的運算式。

位元字串產生器具有以下語法

BitstringPattern <= BitStringExpr

其中 BitStringExpr 是一個評估為位元字串的運算式。

映射產生器具有以下語法

KeyPattern := ValuePattern <- MapExpression

其中 MapExpr 是一個求值結果為 map 的表達式,或是透過呼叫 maps:iterator/1maps:iterator/2 取得的 map 迭代器。

篩選器 是一個求值結果為 truefalse 的表達式。

產生器模式中的變數會遮蔽先前綁定的變數,包括先前產生器模式中綁定的變數。

在產生器表達式中綁定的變數在表達式外部是不可見的。

1> [{E,L} || E <- L=[1,2,3]].
* 1:5: variable 'L' is unbound

列表理解式 會回傳一個列表,其中列表元素是針對所有篩選器都為 true 的產生器元素組合求值 Expr 的結果。

位元字串理解式 會回傳一個位元字串,該字串是透過針對所有篩選器都為 true 的位元字串產生器元素組合,串接求值 BitStringExpr 的結果所建立。

map 理解式 會回傳一個 map,其中 map 元素是針對所有篩選器都為 true 的產生器元素組合,求值 KeyExprValueExpr 的結果。如果鍵表達式不是唯一的,則最後一次出現的值會儲存在 map 中。

範例

將列表中每個元素乘以二

1> [X*2 || X <- [1,2,3]].
[2,4,6]

將二進制資料中每個位元組乘以二,回傳一個列表

1> [X*2 || <<X>> <= <<1,2,3>>].
[2,4,6]

將二進制資料中每個位元組乘以二

1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
<<2,4,6>>

將列表中每個元素乘以二,回傳一個二進制資料

1> << <<(X*2)>> || X <- [1,2,3] >>.
<<2,4,6>>

建立一個從整數到其平方的映射

1> #{X => X*X || X <- [1,2,3]}.
#{1 => 1,2 => 4,3 => 9}

將 map 中每個元素的值乘以二

1> #{K => 2*V || K := V <- #{a => 1,b => 2,c => 3}}.
#{a => 2,b => 4,c => 6}

篩選列表,保留奇數

1> [X || X <- [1,2,3,4,5], X rem 2 =:= 1].
[1,3,5]

篩選列表,只保留匹配的元素

1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
[{a,b},{1,2}]

組合來自兩個列表產生器的元素

1> [{P,Q} || P <- [a,b,c], Q <- [1,2]].
[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

更多範例請參考 程式設計範例

當沒有產生器時,如果所有篩選器都為 true,則理解式會回傳一個由單一元素(求值 Expr 的結果)建構的項,否則會回傳一個由零個元素建構的項(也就是說,列表理解式為 [],位元字串理解式為 <<>>,而 map 理解式為 #{})。

範例

1> [2 || is_integer(2)].
[2]
2> [x || is_integer(x)].
[]

篩選器表達式沒有求值為布林值時會發生什麼,取決於該表達式。

  • 如果該表達式是 守衛表達式,則求值失敗或求值為非布林值等同於求值為 false
  • 如果該表達式不是守衛表達式且求值為非布林值 Val,則會在執行時觸發 {bad_filter, Val} 例外。如果表達式的求值引發例外,則不會被理解式捕獲。

範例(使用守衛表達式作為篩選器)

1> List = [1,2,a,b,c,3,4].
[1,2,a,b,c,3,4]
2> [E || E <- List, E rem 2].
[]
3> [E || E <- List, E rem 2 =:= 0].
[2,4]

範例(使用非守衛表達式作為篩選器)

1> List = [1,2,a,b,c,3,4].
[1,2,a,b,c,3,4]
2> FaultyIsEven = fun(E) -> E rem 2 end.
#Fun<erl_eval.42.17316486>
3> [E || E <- List, FaultyIsEven(E)].
** exception error: bad filter 1
4> IsEven = fun(E) -> E rem 2 =:= 0 end.
#Fun<erl_eval.42.17316486>
5> [E || E <- List, IsEven(E)].
** exception error: an error occurred when evaluating an arithmetic expression
     in operator  rem/2
        called as a rem 2
6> [E || E <- List, is_integer(E), IsEven(E)].
[2,4]

守衛序列

守衛序列 是一連串以分號(;)分隔的守衛。如果至少有一個守衛為 true,則守衛序列為 true。(剩餘的守衛(如果有的話)不會被求值。)

Guard1; ...; GuardK

守衛 是一連串以逗號(,)分隔的守衛表達式。如果所有守衛表達式的求值結果都為 true,則守衛為 true。

GuardExpr1, ..., GuardExprN

守衛表達式

有效的守衛表達式集合是有效 Erlang 表達式集合的子集。限制有效表達式集合的原因是必須保證守衛表達式的求值沒有副作用。有效的守衛表達式如下

  • 變數
  • 常數(原子、整數、浮點數、列表、元組、記錄、二進制資料和 map)
  • 建構原子、整數、浮點數、列表、元組、記錄、二進制資料和 map 的表達式
  • 更新 map 的表達式
  • 記錄表達式 Expr#Name.Field#Name.Field
  • 呼叫在「類型測試 BIF」和「守衛表達式中允許的其他 BIF」表格中指定的 BIF
  • 項比較
  • 算術表達式
  • 布林表達式
  • 短路表達式(andalso/orelse
BIF
is_atom/1
is_binary/1
is_bitstring/1
is_boolean/1
is_float/1
is_function/1
is_function/2
is_integer/1
is_list/1
is_map/1
is_number/1
is_pid/1
is_port/1
is_record/2
is_record/3
is_reference/1
is_tuple/1

表格:類型測試 BIF

請注意,大多數類型測試 BIF 都有較舊的對等版本,它們沒有 is_ 前綴。保留這些舊的 BIF 只是為了向後相容,不應在新程式碼中使用。它們也僅允許在頂層使用。例如,它們不允許在守衛中的布林表達式中使用。

BIF
abs(Number)
bit_size(Bitstring)
byte_size(Bitstring)
element(N, Tuple)
float(Term)
hd(List)
is_map_key(Key, Map)
length(List)
map_get(Key, Map)
map_size(Map)
max(A, B)
min(A, B)
node/0
node(Pid | Ref | Port)
round(Number)
self/0
size(Tuple | Bitstring)
tl(List)
trunc(Number)
tuple_size(Tuple)

表格:守衛表達式中允許的其他 BIF

變更

從 Erlang/OTP 26 開始,允許在守衛中使用 min/2max/2 BIF。

如果算術表達式、布林表達式、短路表達式或呼叫守衛 BIF 失敗(因為引數無效),則整個守衛都會失敗。如果該守衛是守衛序列的一部分,則會評估序列中的下一個守衛(也就是說,下一個分號後的守衛)。

運算子優先順序

運算子優先順序,依遞減順序排列

運算符結合性
#
一元 + - bnot not
/ * div rem band and左結合
+ - bor bxor bsl bsr or xor左結合
++ --右結合
== /= =< < >= > =:= =/=非結合
andalso左結合
orelse左結合
catch
= !右結合
?=非結合

表格:運算子優先順序

變更

在 Erlang/OTP 24 之前,catch 運算子的優先順序最低。

注意

表格中的 = 運算子是比對運算子。字元 = 也可以表示複合模式運算子,該運算子只能在模式中使用。

?= 受限於它只能在 maybe 區塊內的頂層使用。

在評估表達式時,會先評估優先順序最高的運算子。優先順序相同的運算子會根據其結合性進行評估。非結合運算子不能與相同優先順序的運算子組合。

範例

左結合算術運算子從左到右進行評估。

6 + 5 * 4 - 3 / 2 evaluates to
6 + 20 - 1.5 evaluates to
26 - 1.5 evaluates to
24.5

非結合運算子不能組合。

1> 1 < X < 10.
* 1:7: syntax error before: '<'