目前,在 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)
在本规范中,我们建议使用管道符 |
作为可替代模式的分隔符,原因如下
-spec f(foo|bar) -> ok.
),并且这种自然的分隔符选择将是一致的。一个可能的替代方案是使用 ;
分隔符,它目前在保护式、“if”表达式和列表推导式中具有类似于 or
条件分隔项的含义。
但是,分号 (;
) 可能会在语句中识别模式的结尾或分隔表达式时造成混淆。例如
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、receive、try-of 和 fun 表达式中的主体
case Expr of
a ->
b ->
c ->
true;
_ ->
false
end
但是,将相同的原则应用于表示函数调用返回中的可替代模式会导致更多混淆
a -> b -> c = foo()
处理可替代模式有两种方法
Expr1 | Expr2 | ... | ExprN
){a|{b|c, d}} | e | {f|g, [(h|i), (j|k) | [(l|m)]]}
)虽然支持嵌套模式是可取的,但根据实现的复杂性,可能决定不支持无限嵌套。更具体地说,编译器实现嵌套模式的复杂性可能超过此功能的好处。
在可替代模式重叠的情况下,将使用自然的从左到右的评估顺序,类似于 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 通用许可之下,以更宽松的为准。