目前,在 case、receive 運算式、函數子句、lambda、try-of 和 try-catch 中定義模式匹配時,Erlang 僅支援單一匹配,列表和位元字串推導式中的產生器以及函數參數也是如此。
本提案擴展了語法,允許使用多個替代匹配來共享相同的程式碼區塊,或與右側運算式進行匹配。
目前,case、receive、try-of 運算式中的匹配語法如下:
case I of
1 -> less_than_three;
2 -> less_than_three;
3 -> less_than_ten;
_ -> other
end.
應修改為允許以下語法:
case I of
1 | 2 -> less_than_three;
3 -> less_than_ten;
_ -> other
end.
類似地,函數或運算式匹配看起來像這樣:
foo(1) -> ok;
foo(2) -> ok;
foo(N) -> other.
更廣泛來說,case、receive、try-of 運算式、lambda 或模式匹配中的模式,應從以下語法擴展:
case Expr of
Pattern1 [when GuardSeq1] ->
Body1;
...;
PatternN [when GuardSeqN] ->
BodyN
end
receive
Pattern1 [when GuardSeq1] ->
Body1;
...;
PatternN [when GuardSeqN] ->
BodyN
after ExprT ->
BodyT
end
try Exp [of
Pattern1 [when GuardSeq1] ->
Body1;
...;
PatternN [when GuardSeqN] ->
BodyN
]
catch
Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
ExceptionBody1;
...;
ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
ExceptionBodyN
end
Res = ExprF(Expr1,...,ExprN)
以支援以下語法:
case Expr of
Pattern1A |
Pattern1B |
...
Pattern1N [when GuardSeq1] ->
Body1;
...;
PatternNA |
PatternNB |
...
PatternNN [when GuardSeqN] ->
BodyN
end
receive
Pattern1A |
Pattern1B |
...
Pattern1N [when GuardSeq1] ->
Body1;
...;
PatternNA |
PatternNB |
...
PatternNN [when GuardSeqN] ->
BodyN
after ExprT ->
BodyT
end
try Exp [of
Pattern1A |
Pattern1B |
...
Pattern1N [when GuardSeq1] ->
Body1;
...;
PatternNA |
PatternNB |
...
PatternNN [when GuardSeqN] ->
BodyN
]
catch
Class1:ExceptionPattern1A|
ExceptionPattern2A| ...
ExceptionPatternNA[:StackTrace] [when ExceptionGuard1A] ->
ExceptionBodyE1;
ClassN:ExceptionPattern1N|
ExceptionPattern2N| ...
ExceptionPatternNN[:StackTrace] [when ExceptionGuardNN] ->
ExceptionBodyEN
end
Res1 | Res2 | ... | ResN =
ExprF(Expr1, Expr2, ... , ExprN)
ExprF1(Expr1A1 | Expr1A2 | ... | Expr1AN, ...,
Expr1N1 | Expr1N2 | ... | Expr1NN) [when GuardSeq1] -> Body1;
ExprFN(ExprNA1 | ExprNA2 | ... | ExprNAN, ...,
ExprNN1 | ExprNN2 | ... | ExprNNN) [when GuardSeq1] -> Body1
只有在所有替代模式中都綁定變數時,才能在守衛式中使用變數。也就是說,允許以下語法:
case X of
{A, 0} | {0, A} when A > 0 -> ok
end
{A, 1} | {A, 2} = foo()
bar({A, 1} | {A, 2}) -> true
但不允許以下語法:
case X of
{A, 0} |
{0, B} when B > 0 -> ok
end
{A, 1} | {B, 2} = foo()
bar({A, 1} | {B, 2}) -> true
上面顯示的案例會產生以下形式的編譯錯誤:
Error: alternative patterns must have the same variables defined
當使用 `|` 時出現歧義時,例如與列表中的 cons 結合使用時,優先考慮舊的 cons 語法,並可以使用括號來消除替代匹配運算式的歧義:
case X of
[3 | T] -> {ok, T};
[1 | 2 | T] -> error; % Compiler error: ambiguous use of pipe symbol
[(1|2) | T] ->
% Alternative matches, of lists' head being 1 or 2
{ok, T}
end
[(1|2) | T] = check_head_version(L)
在本規格中,我們建議使用管道符號 `|` 作為替代模式的分隔符,原因如下:
一種可能的替代方案是使用 `;` 分隔符,它目前具有類似的含義,即「或」條件分隔守衛式、「if」運算式和列表推導式中的項。
但是,分號(`;`)可能會在識別語句中的模式結尾或分隔運算式時造成混淆。例如:
case Expr of
a ->
true = foo(); % <-- is this the end of the body or a syntax error
% which should be a comma, with the body
% returning 'b'?
b; % <-- is this the alternative pattern or a pattern
% with a missing body?
C when is_integer(C) ->
false
end
此外,`;` 用於分隔 if、case、receive、try-of-catch、fun 和函數中的子句。如果選擇它作為替代模式的分隔符,可能會造成更多的混淆。
另一種可能的替代方案是允許跳過 if、case、recieve、try-of 和 fun 運算式中的程式碼區塊:
case Expr of
a ->
b ->
c ->
true;
_ ->
false
end
但是,將相同的原則應用於表示函數呼叫返回值的替代模式,會造成更多的混淆:
a -> b -> c = foo()
處理替代模式有兩種方法:
雖然支援巢狀模式是理想的,但根據實作的複雜性,可能會決定不支援無限的巢狀。更具體地說,編譯器實作巢狀模式的複雜性可能會超過此功能的好處。
當替代模式重疊時,將使用自然的從左到右的評估順序,類似於 `case` 語句中模式的順序。例如:
{a, X} | {X, b} = foo()
此範例等效於:
case foo() of
Term = {a, X} -> Term;
Term = {X, b} -> Term;
Term -> error({badmatch, Term})
end
實作此提案將會使編寫冗餘匹配子句的方式更加方便,並消除程式碼重複。
可以在現有的 OTP 程式碼的許多地方找到這可能有用的範例。例如,ssl_logger.erl。
case logger:compare_levels(Level, debug) of
lt ->
?LOG_DEBUG(#{direction => Direction,
protocol => Protocol,
message => Message},
#{domain => [otp,ssl,Protocol]});
eq ->
?LOG_DEBUG(#{direction => Direction,
protocol => Protocol,
message => Message},
#{domain => [otp,ssl,Protocol]});
_ ->
ok
end.
case logger:compare_levels(Level, notice) of
lt ->
?LOG_NOTICE(Report);
eq ->
?LOG_NOTICE(Report);
_ ->
ok
end.
此外,此語法擴展可以使函數規格定義及其實作之間保持一致性。
-spec f(foo|bar) -> ok.
f(foo|bar) -> ok.
任何使用 case 和 receive 運算式舊實作的程式碼都將繼續像今天一樣工作,並產生相同的結果。
作為實作建議,可以透過複製包含管道符號的替代模式來重寫 AST,因此 `X of lt | gt -> ok end` 的情況變成 `case X of lt -> ok; gt -> ok end`。對於匹配運算式,它可以將 `lt | gt = compare(A, B)` 重寫為 case 語句 `case compare(A, B) of lt -> lt; gt -> gt end`,而巢狀替代模式可以使用守衛式來重寫匹配,例如:函數參數匹配 `foo(lt | gt, a | b) -> true` 將會變成:
foo(lt | gt, a | b) -> true
變成:
foo(A, B) ->
case A of
_ when A =:= lt; A =:= gt ->
case B of
_ when B =:= a; B =:= b ->
. . .
end
end
編譯器在列表推導式中重寫替代模式會帶來額外的挑戰。實作建議包括以下內容:
[X || {a,X} | {b,X} <- L]
變成:
[case I of
{a, X} | {b, X} -> X
end
|| I <- L,
case I of
{a, _} | {b, _} -> true;
_ -> false
end
]
或
[X || {X} <-
[case Item of
{a,X0} | {b,X0} ->
{X0}; % Set of matched variables
_ ->
nomatch
end || Item <- L]]
本文件已置於公有領域或 CC0-1.0-通用許可證之下,以更寬容的許可證為準。