9 表达式
在本节中,列出了所有有效的 Erlang 表达式。在编写 Erlang 程序时,还可以使用宏表达式和记录表达式。但是,这些表达式在编译期间会被扩展,因此从某种意义上说它们不是真正的 Erlang 表达式。宏表达式和记录表达式将在单独的章节中介绍
9.1 表达式求值
除非明确说明,否则在表达式本身求值之前,所有子表达式都会先进行求值。例如,考虑表达式
Expr1 + Expr2
Expr1 和 Expr2 也是表达式,它们会先进行求值(以任何顺序)然后才进行加法运算。
许多运算符只能应用于特定类型的参数。例如,算术运算符只能应用于数字。如果参数类型错误,会导致运行时错误 badarg。
9.2 项
最简单的表达式形式是项,即整数、浮点数、原子、字符串、列表、映射或元组。返回值是项本身。
9.3 变量
变量是一个表达式。如果变量绑定到一个值,则返回值就是这个值。未绑定的变量仅允许在模式中使用。
变量以大写字母或下划线 (_) 开头。变量可以包含字母数字字符、下划线和 @。
示例
X Name1 PhoneNumber Phone_number _ _Height
变量使用 模式匹配 绑定到值。Erlang 使用 **单赋值**,即变量只能绑定一次。
**匿名变量** 用下划线 (_) 表示,当需要变量但其值可以忽略时可以使用。
示例
[H|_] = [1,2,3]
以下划线 (_) 开头的变量,例如 _Height,是普通变量,而不是匿名变量。但是,编译器会忽略它们,因为它们不会生成警告。
示例
以下代码
member(_, []) -> [].
可以改写成更易读的形式
member(Elem, []) -> [].
如果代码在设置了标志 warn_unused_vars 的情况下进行编译,这会导致未使用变量 Elem 的警告。相反,代码可以改写为
member(_Elem, []) -> [].
请注意,由于以下划线开头的变量不是匿名变量,因此这将匹配
{_,_} = {1,2}
但它会失败
{_N,_N} = {1,2}
变量的作用域是其函数子句。在 if、case 或 receive 表达式分支中绑定的变量必须在所有分支中都绑定,才能在表达式之外拥有值。否则,它们在表达式之外会被视为“不安全”。
对于 try 表达式,变量作用域有限,因此在表达式中绑定的变量始终在表达式之外为“不安全”。
9.4 模式
模式与项具有相同的结构,但可以包含未绑定的变量。
示例
Name1 [H|T] {error,Reason}
模式允许在子句头部、case 表达式、receive 表达式 和 匹配表达式 中使用。
复合模式运算符
如果 Pattern1 和 Pattern2 是有效的模式,则以下也是有效的模式
Pattern1 = Pattern2
当与项匹配时,Pattern1 和 Pattern2 都将与该项匹配。此功能背后的理念是避免对项进行重建。
示例
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} -> ...
9.5 匹配运算符
以下将 Pattern 与 Expr 匹配
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
9.6 匹配运算符和复合模式运算符
这是一个高级部分,它引用了尚未介绍的主题。在初次阅读时,可以跳过此部分。
= 字符用于表示两个类似但不同的运算符:匹配运算符和复合模式运算符。哪个运算符是目标取决于上下文。
**复合模式运算符** 用于从两个模式构造一个复合模式。复合模式在任何接受模式的地方都被接受。如果所有构成模式都匹配,则复合模式匹配。在复合模式的一部分模式中,使用在同一复合模式的其他子模式中绑定的变量(作为映射模式中的键或二进制模式中的大小)是非法的。
示例
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。然后它将 M 的值与模式 #{Key := Value} 匹配,使用变量 Key 作为键,绑定变量 Value。
提示 3> 处的表达式将表达式 (#{key := Key} = M) 与模式 #{Key := Value} 匹配。括号内的表达式首先进行求值。也就是说,M 与 #{key := Key} 匹配,然后 M 的值与模式 #{Key := Value} 匹配。这与 2 中的求值顺序相同;因此,括号是多余的。
在提示 4> 处的表达式中,表达式 M 与括号内的模式匹配。由于括号内的结构是一个模式,因此将两个模式分开的 = 是复合模式运算符(**不是**匹配运算符)。匹配失败,因为两个子模式同时进行匹配,因此在与模式 #{Key := Value} 匹配时,变量 Key 不会被绑定。
在提示 5> 处的表达式中,块表达式 内的表达式首先进行求值,绑定变量 Y 并创建二进制。然后,将二进制与模式 <<X:Y>> 匹配,使用 Y 的值作为段的大小。
9.7 函数调用
ExprF(Expr1,...,ExprN) ExprM:ExprF(Expr1,...,ExprN)
在函数调用的第一种形式 ExprM:ExprF(Expr1,...,ExprN) 中,ExprM 和 ExprF 中的每一个都必须是一个原子,或者是一个求值为原子的表达式。据说该函数是通过使用 **完全限定函数名** 调用的。这通常被称为 **远程** 或 **外部函数调用**。
示例
lists:keysearch(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 不被自动导入。在某些情况下,这种编译指令是必需的。
在 OTP R14A(ERTS 版本 5.8)之前,对与自动导入的 BIF 具有相同名称的函数的隐式限定函数调用始终导致调用 BIF。在更新版本的编译器中,将改为调用本地函数。这是为了避免将来对自动导入的 BIF 集的添加不会默默地改变旧代码的行为。
但是,为了避免旧的(R14 之前的)代码在使用 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]) 指令,编译器将在模块中使用隐式限定函数名调用该函数时发出警告。
9.8 如果
if GuardSeq1 -> Body1; ...; GuardSeqN -> BodyN end
按顺序扫描 if 表达式的分支,直到找到评估为真的保护序列 GuardSeq。然后,评估相应的 Body(以 ',' 分隔的表达式序列)。
Body 的返回值是 if 表达式的返回值。
如果没有任何保护序列被评估为真,则会发生 if_clause 运行时错误。如果需要,保护表达式 true 可以用在最后一个分支中,因为该保护序列始终为真。
示例
is_greater_than(X, Y) -> if X>Y -> true; true -> % works as an 'else' branch false end
9.9 情况
case Expr of Pattern1 [when GuardSeq1] -> Body1; ...; PatternN [when GuardSeqN] -> BodyN end
评估表达式 Expr,并按顺序将模式 Pattern 与结果匹配。如果匹配成功并且可选的保护序列 GuardSeq 为真,则评估相应的 Body。
Body 的返回值是 case 表达式的返回值。
如果没有带有真保护序列的匹配模式,则会发生 case_clause 运行时错误。
示例
is_valid_signal(Signal) -> case Signal of {signal, _What, _From, _To} -> true; {signal, _What, _To} -> true; _Else -> false end.
9.10 可能
maybe 是在 Erlang/OTP 25 中引入的一个实验性 特性。默认情况下,它是禁用的。要启用 maybe,请使用 -feature(maybe_expr,enable) 指令(从源代码中),或使用编译器选项 {feature,maybe_expr,enable}。
maybe Expr1, ..., ExprN end
按顺序评估 maybe 块中的表达式。如果所有表达式都成功评估,则 maybe 块的返回值为 ExprN。但是,执行可以通过条件匹配表达式进行短路
Expr1 ?= Expr2
?= 被称为条件匹配运算符。它只能在 maybe 块的顶层使用。它将模式 Expr1 与 Expr2 匹配。如果匹配成功,则模式中任何未绑定的变量都会被绑定。如果该表达式是 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() 返回 -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
可以使用 else 子句来增强 maybe 块
maybe Expr1, ..., ExprN else Pattern1 [when GuardSeq1] -> Body1; ...; PatternN [when GuardSeqN] -> BodyN end
如果条件匹配运算符失败,则将失败的表达式与 else 和 end 关键字之间的所有子句中的模式匹配。如果匹配成功并且可选的保护序列 GuardSeq 为真,则评估相应的 Body。从主体返回的值是 maybe 块的返回值。
如果没有带有真保护序列的匹配模式,则会发生 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 运行时错误。
9.11 发送
Expr1 ! Expr2
将 Expr2 的值作为消息发送到由 Expr1 指定的进程。 Expr2 的值也是该表达式的返回值。
Expr1 必须评估为 pid、别名(引用)、端口、已注册的名称(原子)或元组 {Name,Node}。 Name 是一个原子,而 Node 是一个节点名称,也是一个原子。
- 如果 Expr1 评估为名称,但该名称未注册,则会发生 badarg 运行时错误。
- 向引用发送消息永远不会失败,即使该引用不再(或从未是)别名。
- 向 pid 发送消息永远不会失败,即使 pid 标识了一个不存在的进程。
- 分布式消息发送,即如果 Expr1 评估为元组 {Name,Node}(或位于另一个节点的 pid),也永远不会失败。
9.12 接收
receive Pattern1 [when GuardSeq1] -> Body1; ...; PatternN [when GuardSeqN] -> BodyN end
从进程的消息队列中获取接收到的消息。消息队列中的第一条消息将按顺序从上到下与模式匹配。如果未找到匹配项,则对队列中的第二条消息重复匹配序列,依此类推。消息在 接收它们的顺序 中排队。如果匹配成功,也就是说,如果 Pattern 匹配并且可选的保护序列 GuardSeq 为真,则将消息从消息队列中移除并评估相应的 Body。消息队列中的所有其他消息保持不变。
Body 的返回值是 receive 表达式的返回值。
receive 永远不会失败。执行将暂停,可能无限期地暂停,直到收到与其中一个模式匹配并且保护序列为真的消息。
示例
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 必须评估为整数,或原子 infinity。允许的整数范围为 0 到 4294967295,也就是说,最长的超时时间几乎是 50 天。如果消息队列中没有匹配的消息,则使用零值将立即发生超时。
原子 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.
9.13 术语比较
Expr1 op Expr2
op | 描述 |
== | 等于 |
/= | 不等于 |
=< | 小于或等于 |
< | 小于 |
>= | 大于或等于 |
> | 大于 |
=:= | 完全等于 |
=/= | 完全不等于 |
参数可以是不同的数据类型。定义了以下顺序
number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string
nil 在前面的表达式中代表空列表 ([]),它被视为与 list/0 不同的类型。这就是为什么 nil < list 的原因。
列表按元素比较。元组按大小排序,两个大小相同的元组按元素比较。
位串按位比较。如果一个位串是另一个位串的前缀,则较短的位串被认为更小。
映射按大小排序,两个大小相同的映射按键按升序术语顺序比较,然后按键顺序比较值。在映射中,整数类型被认为小于浮点数类型。
原子通过其字符串值逐个代码点进行比较。
比较整数和浮点数时,精度较低的项会被转换为另一项的类型,除非运算符为 =:= 或 =/=。当浮点数的所有有效数字都在小数点左侧时,浮点数比整数更精确。当浮点数大于/小于 +/-9007199254740992.0 时,就会发生这种情况。转换策略会根据浮点数的大小而改变,因为否则比较大型浮点数和整数会失去传递性。
项比较运算符返回表达式的布尔值,即 true 或 false。
示例
1> 1==1.0. true 2> 1=:=1.0. false 3> 1 > a. false 4> #{c => 3} > #{a => 1, b => 2}. false 5> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. true 6> <<2:2>> < <<128>>. true 7> <<3:2>> < <<128>>. false
9.14 算术表达式
op Expr Expr1 op Expr2
运算符 | 描述 | 参数类型 |
+ | 一元 + | 数字 |
- | 一元 - | 数字 |
+ | 数字 | |
- | 数字 | |
* | 数字 | |
/ | 浮点除法 | 数字 |
bnot | 一元按位非 | 整数 |
div | 整数除法 | 整数 |
rem | X/Y 的整数余数 | 整数 |
band | 按位与 | 整数 |
bor | 按位或 | 整数 |
bxor | 算术按位异或 | 整数 |
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
9.15 布尔表达式
op Expr Expr1 op Expr2
运算符 | 描述 |
not | 一元逻辑非 |
and | 逻辑与 |
or | 逻辑或 |
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
9.16 短路表达式
Expr1 orelse Expr2 Expr1 andalso Expr2
Expr2 仅在必要时才进行求值。也就是说,只有在以下情况下才会求值 Expr2:
-
Expr1 在 orelse 表达式中求值为 false。
or
-
Expr1 在 andalso 表达式中求值为 true。
返回 Expr1 的值(即 true 或 false)或 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 不需要求值为布尔值。因此,andalso 和 orelse 是尾递归的。
示例 3(尾递归函数)
all(Pred, [Hd|Tail]) -> Pred(Hd) andalso all(Pred, Tail); all(_, []) -> true.
在 Erlang/OTP R13A 之前,Expr2 需要求值为布尔值,因此,andalso 和 orelse **不是** 尾递归的。
9.17 列表操作
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]
9.18 地图表达式
创建地图
构造新地图是通过让表达式 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
这里,A 和 B 是任何表达式,M0 到 M4 是生成的地图项。
如果声明了两个匹配的键,则后面的键优先。
示例
1> #{1 => a, 1 => b}. #{1 => b } 2> #{1.0 => a, 1 => b}. #{1 => b, 1.0 => a}
构造键(及其关联值)的表达式的求值顺序未定义。构造中键值对的语法顺序无关紧要,除了上面提到的两个匹配键的情况。
更新地图
更新地图的语法与构造地图类似。
定义要更新的地图的表达式位于定义要更新的键及其相应值的表达式之前
M#{ K => V }
这里 M 是类型为地图的项,K 和 V 是任何表达式。
如果键 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 是任何地图。因此,M1 .. M4 也是地图。
更多示例
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
这里键 K1 .. Kn 是任何具有文字或绑定变量的表达式。如果所有键表达式都成功求值,并且所有键都存在于地图 M 中,则所有变量 V1 .. Vn 都将与它们各自键的关联值匹配。
如果匹配条件不满足,则匹配失败,结果是:
-
一个 badmatch 异常。
如果它在匹配运算符的上下文中使用,如示例所示,则会发生这种情况。
-
或者在函数头和 case 表达式中导致测试下一个子句。
地图中的匹配仅允许 := 作为关联的分隔符。
声明键的顺序无关紧要。
匹配中允许重复键,并匹配与这些键关联的每个模式
#{ 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 }};
守卫中的地图
只要所有子表达式都是有效的守卫表达式,地图就可以在守卫中使用。
以下守卫 BIF 处理地图
- is_map/1 在 erlang 模块中
- is_map_key/2 在 erlang 模块中
- map_get/2 在 erlang 模块中
- map_size/1 在 erlang 模块中
9.19 位语法表达式
位语法对 **位字符串** 进行操作。位字符串是按从最高有效位到最低有效位的顺序排列的一系列位。
<<>> % 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 必须是一个 守卫表达式,该表达式求值为整数。守卫表达式中的所有变量都必须已经绑定。
在 Erlang/OTP 23 之前,Size 被限制为整数或绑定到整数的变量。
Size 的值指定段的大小(以单位为单位,见下文)。默认值取决于类型(见下文)。
- 对于 integer,它为 8。
- 对于 float,它为 64。
- 对于 binary 和 bitstring,它为整个二进制文件或位字符串。
在匹配中,二进制文件或位字符串段的默认值仅对最后一个元素有效。匹配中的所有其他位字符串或二进制元素都必须具有大小规范。
二进制文件
长度是 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">>
对于 utf8、utf16 和 utf32 类型,不能指定 Size。段的大小由类型和值本身隐式确定。
TypeSpecifierList 是一个类型说明符列表,以任何顺序排列,用连字符 (-) 分隔。任何省略的类型说明符都使用默认值。
- Type= integer | float | binary | bytes | bitstring | bits | utf8 | utf16 | utf32
- 默认值为 integer。 bytes 是 binary 的简写,bits 是 bitstring 的简写。有关 utf 类型的更多信息,请参见下文。
- Signedness= signed | unsigned
- 仅在匹配时以及类型为 integer 时才重要。默认值为 unsigned。
- Endianness= big | little | native
- 指定字节级别(八位字节级别)的字节序(字节顺序)。本机字节序意味着字节序在加载时解析为大端序或小端序,具体取决于运行 Erlang 机器 CPU 的本机字节序。字节序仅在类型为 integer、utf16、utf32 或 float 时才重要。默认值为 big。
<<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
- Unit= unit:IntegerLiteral
- 允许的范围是 1 到 256。对于 integer、float 和 bitstring,默认值为 1,对于 binary,默认值为 8。对于类型 bitstring、bits 和 bytes,不允许指定与默认值不同的单位值。对于类型 utf8、utf16 和 utf32,不应指定任何单位说明符。
整数段
Size 的值乘以单位给出段的大小(以位为单位)。
构造位串时,如果整数段的大小 N 太小而无法容纳给定的整数,则会静默丢弃整数的最高有效位,并且仅将 N 个最低有效位放入位串。例如,<<16#ff:4>> 将导致位串 <<15:4>>。
浮点段
Size 的值乘以单位给出段的大小(以位为单位)。浮点段的大小(以位为单位)必须是 16、32 或 64 之一。
构造位串时,如果浮点段的大小太小而无法容纳给定浮点值的表示形式,则会引发异常。
匹配位串时,如果段的位不包含有限浮点值的表示形式,则浮点段的匹配将失败。
二进制段
在本节中,短语“二进制段”指的是任何一种段类型 binary、bitstring、bytes 和 bits。
另请参见有关 二进制文件 的段落。
构造二进制文件时,如果未为二进制段指定大小,则将整个二进制值插入到正在构造的二进制文件中。但是,被插入的二进制文件的大小(以位为单位)必须能被段的单位值整除;否则会引发异常。
例如,以下示例均成功
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 段
类型 utf8、utf16 和 utf32 分别指定 Unicode 转换格式 UTF-8、UTF-16 和 UTF-32 的编码/解码。
构造 utf 类型的段时,Value 必须是 0 到 16#D7FF 或 16#E000 到 16#10FFFF 范围内的整数。如果 Value 超出允许的范围,构造将失败并出现 badarg 异常。编码值的尺寸如下所示
- 对于 utf8,Value 编码为 1-4 个字节。
- 对于 utf16,Value 编码为 2 或 4 个字节。
- 对于 utf32,Value 编码为 4 个字节。
构造时,可以给出文字字符串,后跟 UTF 类型之一,例如:<<"abc"/utf8>>,它是 <<$a/utf8,$b/utf8,$c/utf8>> 的语法糖。
utf 类型段的成功匹配将导致 0 到 16#D7FF 或 16#E000 到 16#10FFFF 范围内的整数。如果返回的值超出这些范围,则匹配将失败。
类型为 utf8 的段匹配位串中的 1-4 个字节,如果位串在匹配位置包含有效的 UTF-8 序列。(参见 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>>”。
更多示例在 编程示例 中提供。
9.20 函数表达式
fun [Name](Pattern11,...,Pattern1N) [when GuardSeq1] -> Body1; ...; [Name](PatternK1,...,PatternKN) [when GuardSeqK] -> BodyK end
函数表达式以关键字 fun 开始,以关键字 end 结束。它们之间应该是函数声明,类似于 常规函数声明,不同之处在于函数名称是可选的,如果存在,则应该是变量。
函数头中的变量会遮蔽函数名称,并且两者都会遮蔽围绕函数表达式所在函数子句的变量。在函数体中绑定的变量是函数体中的局部变量。
表达式的返回值是结果函数。
示例
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 Name/Arity fun Module:Name/Arity
在 Name/Arity 中,Name 是一个原子,Arity 是一个整数。 Name/Arity 必须指定现有的局部函数。该表达式是以下表达式的语法糖:
fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end
在 Module:Name/Arity 中,Module 和 Name 是原子,Arity 是一个整数。 Module、Name 和 Arity 也可以是变量。以这种方式定义的函数引用模块 Module 的 最新 版本中的函数 Name,其元数为 Arity。以这种方式定义的函数不依赖于定义它的模块的代码。
在 Erlang/OTP R15 之前,不允许 Module、Name 和 Arity 为变量。
更多示例在 编程示例 中提供。
9.21 捕获和抛出
catch Expr
返回 Expr 的值,除非在评估期间发生异常。在这种情况下,会捕获异常。
对于类 error 的异常(即运行时错误),将返回 {'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 内评估,并返回值 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
9.22 尝试
try Exprs catch Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] -> ExceptionBody1; ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] -> ExceptionBodyN end
这是对 捕获 的增强。它提供了以下可能性:
- 区分不同的异常类别。
- 选择仅处理所需类别。
- 将其他类别传递给封闭的 try 或 catch,或传递给默认错误处理。
请注意,虽然在 try 表达式中使用关键字 catch,但 try 表达式中没有 catch 表达式。
它返回 Exprs(表达式序列 Expr1, ..., ExprN)的值,除非在评估期间发生异常。在这种情况下,会捕获异常,并且具有正确异常类别 Class 的模式 ExceptionPattern 会依次与捕获的异常进行匹配。如果匹配成功,并且可选的保护序列 ExceptionGuardSeq 为 true,则相应的 ExceptionBody 将被评估并成为返回值。
如果指定了 Stacktrace,则它必须是变量的名称(而不是模式)。当相应的 ExceptionPattern 匹配时,堆栈跟踪将绑定到变量。
如果在评估 Exprs 期间发生异常,但没有具有正确 Class 且保护序列为 true 的匹配 ExceptionPattern,则会将异常传递出去,就像 Exprs 没有包含在 try 表达式中一样。
如果在评估 ExceptionBody 期间发生异常,则不会捕获该异常。
允许省略 Class 和 Stacktrace。省略的 Class 是 throw 的简写
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
AfterBody 在 Body 或 ExceptionBody 之后评估,无论哪个。AfterBody 的评估结果将丢失;try 表达式的返回值与有 after 部分和没有 after 部分相同。
即使在 Body 或 ExceptionBody 的评估期间发生异常,AfterBody 也会被评估。在这种情况下,异常将在 AfterBody 评估之后传递,因此来自 try 表达式的异常与有 after 部分和没有 after 部分相同。
如果在 AfterBody 本身的评估期间发生异常,则不会被捕获。因此,如果 AfterBody 在 Exprs、Body 或 ExceptionBody 中的异常之后评估,则该异常将丢失并被 AfterBody 中的异常掩盖。
of、catch 和 after 部分都是可选的,只要至少有一个 catch 或 after 部分。因此,以下都是有效的 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/2 或 binary_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 部分中绑定
- 在 catch 和 after 部分以及整个结构之后都不安全
在 of 部分中绑定的变量是
- 在 catch 部分中未绑定
- 在 after 部分以及整个结构之后都不安全
在 catch 部分中绑定的变量在 after 部分以及整个结构之后都不安全。
在 after 部分中绑定的变量在整个结构之后都不安全。
9.23 带括号的表达式
(Expr)
带括号的表达式用于覆盖 运算符优先级,例如,在算术表达式中
1> 1 + 2 * 3. 7 2> (1 + 2) * 3. 9
9.24 块表达式
begin Expr1, ..., ExprN end
块表达式提供了一种将一系列表达式分组的方法,类似于子句体。返回值是最后一个表达式 ExprN 的值。
9.25 推导式
推导式提供了一种简洁的符号,用于遍历一个或多个项并构建一个新项。推导式根据它们构建的项类型分为三种不同的类型。
列表推导式构建列表。它们具有以下语法
[Expr || Qualifier1, . . ., QualifierN]
这里,Expr 是一个任意表达式,每个 Qualifier 都是一个 **生成器** 或一个 **过滤器**。
位串推导式构建位串或二进制文件。它们具有以下语法
<< BitStringExpr || Qualifier1, . . ., QualifierN >>
BitStringExpr 是一个计算结果为位串的表达式。如果 BitStringExpr 是一个函数调用,则它必须用括号括起来。每个 Qualifier 都是一个 **生成器** 或一个 **过滤器**。
地图推导式构建映射。它们具有以下语法
#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}
这里,KeyExpr 和 ValueExpr 是任意表达式,每个 Qualifier 都是一个 **生成器** 或一个 **过滤器**。
地图推导式和地图生成器是在 Erlang/OTP 26 中引入的。
生成器有三种类型。
**列表生成器** 具有以下语法
Pattern <- ListExpr
其中 ListExpr 是一个计算结果为项列表的表达式。
**位串生成器** 具有以下语法
BitstringPattern <= BitStringExpr
其中 BitStringExpr 是一个计算结果为位串的表达式。
**地图生成器** 具有以下语法
KeyPattern := ValuePattern <- MapExpression
其中 MapExpr 是一个计算结果为映射的表达式,或通过调用 maps:iterator/1 或 maps:iterator/2 获得的地图迭代器。
**过滤器** 是一个计算结果为 true 或 false 的表达式。
生成器模式中的变量会隐藏之前绑定的变量,包括之前生成器模式中绑定的变量。
在生成器表达式中绑定的变量在表达式外部不可见
1> [{E,L} || E <- L=[1,2,3]].
* 1:5: variable 'L' is unbound
**列表推导式** 返回一个列表,其中列表元素是针对所有过滤器都为真的每个生成器元素组合评估 Expr 的结果。
**位串推导式** 返回一个位串,该位串是通过连接针对所有过滤器都为真的每个位串生成器元素组合评估 BitStringExpr 的结果而创建的。
**地图推导式** 返回一个映射,其中映射元素是针对所有过滤器都为真的每个生成器元素组合评估 KeyExpr 和 ValueExpr 的结果。如果键表达式不唯一,则存储最后一个出现的表达式。
示例
将列表中的每个元素乘以 2
1> [X*2 || X <- [1,2,3]].
[2,4,6]
将二进制文件中的每个字节乘以 2,返回一个列表
1> [X*2 || <<X>> <= <<1,2,3>>].
[2,4,6]
将二进制文件中的每个字节乘以 2
1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
<<2,4,6>>
将列表中的每个元素乘以 2,返回一个二进制文件
1> << <<(X*2)>> || X <- [1,2,3] >>.
<<2,4,6>>
创建一个从整数到其平方的映射
1> #{X => X*X || X <- [1,2,3]}.
#{1 => 1,2 => 4,3 => 9}
将映射中每个元素的值乘以 2
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}]
在 编程示例 中提供了更多示例。
当没有生成器时,推导式将返回一个从单个元素(评估 Expr 的结果)构建的项(如果所有过滤器都为真),或一个从没有元素构建的项(即,列表推导式为 [],位串推导式为 <<>>,地图推导式为 #{})。
示例
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]
9.26 守卫序列
**守卫序列** 是一系列守卫,用分号 (;) 分隔。如果至少有一个守卫为真,则守卫序列为真。(如果还有其他守卫,则不会评估这些守卫。)
Guard1;...;GuardK
**守卫** 是一系列守卫表达式,用逗号 (,) 分隔。如果所有守卫表达式都计算为 true,则守卫为真。
GuardExpr1,...,GuardExprN
9.27 守卫表达式
有效 **守卫表达式** 的集合是有效 Erlang 表达式集合的子集。限制有效表达式集合的原因是必须保证守卫表达式的评估没有副作用。有效的守卫表达式如下
- 变量
- 常量(原子、整数、浮点数、列表、元组、记录、二进制文件和映射)
- 构建原子、整数、浮点数、列表、元组、记录、二进制文件和映射的表达式
- 更新映射的表达式
- 记录表达式 Expr#Name.Field 和 #Name.Field
- 对表 **类型测试 BIF** 和 **守卫表达式中允许的其他 BIF** 中指定的 BIF 的调用
- 项比较
- 算术表达式
- 布尔表达式
- 短路表达式 (andalso/orelse)
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 都有旧的等效项,没有 is_ 前缀。这些旧的 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() |
node(Pid|Ref|Port) |
round(Number) |
self() |
size(Tuple|Bitstring) |
tl(List) |
trunc(Number) |
tuple_size(Tuple) |
从 Erlang/OTP 26 开始,允许在守卫中使用 min/2 和 max/2 BIF。
如果算术表达式、布尔表达式、短路表达式或对守卫 BIF 的调用失败(由于参数无效),则整个守卫将失败。如果守卫是守卫序列的一部分,则评估序列中的下一个守卫(即,下一个分号后的守卫)。
9.28 运算符优先级
运算符优先级降序
: | |
# | |
一元 + - bnot not | |
/ * div rem band and | 左结合 |
+ - bor bxor bsl bsr or xor | 左结合 |
++ -- | 右结合 |
== /= =< < >= > =:= =/= | 非结合 |
andalso | 左结合 |
orelse | 左结合 |
catch | |
= ! | 右结合 |
?= | 非结合 |
在 Erlang/OTP 24 之前,catch 运算符的优先级最低。
评估表达式时,优先级最高的运算符首先被评估。优先级相同的运算符根据它们的结合性进行评估。非结合运算符不能与优先级相同的运算符组合使用。
示例
左结合的算术运算符从左到右进行评估
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: '<'