查看源代码 表达式

本节列出了所有有效的 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 表达式匹配表达式中使用。

复合模式运算符

如果 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 的守卫序列 GuardSeq。然后,计算对应的 Body(由逗号 , 分隔的表达式序列)。

Body 的返回值是 if 表达式的返回值。

如果没有任何守卫序列的求值结果为 true,则会发生 if_clause 运行时错误。如有必要,可以在最后一个分支中使用守卫表达式 true,因为该守卫序列始终为 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 依次与结果进行匹配。如果匹配成功,并且可选的守卫序列 GuardSeq 为 true,则计算对应的 Body

Body 的返回值是 case 表达式的返回值。

如果没有匹配的模式且守卫序列为 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 关键字之间的所有子句中的模式进行匹配。如果匹配成功,并且可选的守卫序列 GuardSeq 为 true,则计算对应的 Body。从 Body 返回的值是 maybe 代码块的返回值。

如果没有匹配的模式且守卫序列为 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、别名(引用)、端口、注册名称(原子)或元组 {Name,Node}Name 是一个原子,Node 是一个节点名称,也是一个原子。

  • 如果 Expr1 计算为名称,但此名称未注册,则会发生 badarg 运行时错误。
  • 向引用发送消息永远不会失败,即使该引用不再是(或从未是)别名也是如此。
  • 向 pid 发送消息永远不会失败,即使该 pid 标识的是不存在的进程也是如此。
  • 分布式消息发送,即如果 Expr1 计算为元组 {Name,Node}(或位于另一个节点上的 pid),也永远不会失败。

Receive

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

提取进程消息队列中存在的已接收消息。消息队列中的第一条消息会从上到下依次与模式进行匹配。如果未找到匹配项,则对队列中的第二条消息重复匹配序列,依此类推。消息按照接收顺序排队。如果匹配成功,即如果 Pattern 匹配并且可选的守卫序列 GuardSeq 为 true,则该消息将从消息队列中删除,并计算对应的 Body。消息队列中的所有其他消息保持不变。

Body 的返回值是 receive 表达式的返回值。

receive 永远不会失败。执行会暂停,可能会无限期地暂停,直到收到一条与模式之一匹配且守卫序列为 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 必须计算为整数或原子 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.

有关 Erlang 中计时器的更多信息,请参阅 ERTS 用户指南的计时器部分,即Erlang 中的时间和时间校正

项比较

Expr1 op Expr2
op描述
==等于
/=不等于
=<小于或等于
<小于
>=大于或等于
>大于
=:=项等价
=/=项非等价

表:项比较运算符。

参数可以是不同的数据类型。定义了以下顺序

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

上一个表达式中的 nil 表示空列表 ([]),它被视为与 list/0 分开的类型。这就是为什么 nil < list

列表按元素进行比较。元组按大小排序,两个大小相同的元组按元素进行比较。

位字符串按位进行比较。如果一个位字符串是另一个位字符串的前缀,则较短的位字符串被认为较小。

映射按大小排序,两个大小相同的映射按升序项顺序的键进行比较,然后按键顺序的值进行比较。在映射键顺序中,整数类型被认为小于浮点类型。

原子使用其字符串值按代码点进行比较。

当将整数与浮点数进行比较时,除非运算符是 =:==/= 之一,否则精度较低的项将转换为另一个项的类型。在浮点的所有有效数字都位于小数点左侧之前,浮点数比整数更精确。当浮点数大于/小于 +/-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
运算符描述参数类型
+一元 +数值
-求反(一元 -)数值
+加法数值
-减法数值
*乘法数值
/浮点除法数值
bnot一元按位非整数
div整数除法整数
remX/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

布尔表达式

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

短路表达式

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}};

守卫中的映射

只要所有子表达式都是有效的守卫表达式,就允许在守卫中使用映射。

以下守卫 BIF 处理映射:

位语法表达式

位语法对位字符串进行操作。位字符串是一个位序列,从最高有效位到最低有效位排序。

<<>>  % 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。
  • 对于 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 - 指定字节级(八位字节级)的字节序(字节顺序)。Native-endian 表示字节序在加载时被解析为大端或小端,具体取决于 Erlang 虚拟机运行的 CPU 的本机字节序。仅当 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 内的整数。如果返回的值超出这些范围,则匹配失败。

utf8 类型的段在位串中匹配 1-4 个字节,如果位串在匹配位置包含有效的 UTF-8 序列。(请参见 RFC-3629 或 Unicode 标准。)

utf16 类型的段可以在位串中匹配 2 或 4 个字节。如果位串在匹配位置不包含 Unicode 代码点的合法 UTF-16 编码,则匹配失败。(请参见 RFC-2781 或 Unicode 标准。)

utf32 类型的段可以在位串中匹配 4 个字节,方式与 integer 段匹配 32 位的方式相同。如果生成的整数超出前面提到的合法范围,则匹配失败。

示例

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 的函数 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

如果在 catch 内未评估 throw/1,则会发生 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,或者传递给默认的错误处理程序。

请注意,尽管在 try 表达式中使用了关键字 catch,但在 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

AfterBody 会在 BodyExceptionBody 之后进行求值,无论执行哪个。 AfterBody 的求值结果将会丢失;带有 after 部分的 try 表达式的返回值与不带 after 部分的返回值相同。

即使在对 BodyExceptionBody 求值期间发生异常,也会对 AfterBody 进行求值。在这种情况下,异常会在 AfterBody 求值之后传递出去,因此带有 after 部分的 try 表达式产生的异常与不带 after 部分的异常相同。

如果在求值 AfterBody 本身期间发生异常,则不会捕获该异常。因此,如果在 ExprsBodyExceptionBody 中发生异常后对 AfterBody 进行求值,则该异常会丢失并被 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 的值。

推导式

推导式提供了一种简洁的表示法,用于迭代一个或多个项并构造一个新项。推导式有三种不同的类型,具体取决于它们构建的项的类型。

列表推导式构造列表。它们具有以下语法:

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

此处,Expr 是一个任意表达式,每个 Qualifier 都是一个 **生成器** 或一个 **过滤器**。

位串推导式构造位串或二进制数据。它们具有以下语法:

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

BitStringExpr 是一个求值为位串的表达式。如果 BitStringExpr 是一个函数调用,则必须将其括在括号中。每个 Qualifier 都是一个 **生成器** 或一个 **过滤器**。

映射推导式构造映射。它们具有以下语法:

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

此处,KeyExprValueExpr 是任意表达式,每个 Qualifier 都是一个 **生成器** 或一个 **过滤器**。

更改

映射推导式和映射生成器在 Erlang/OTP 26 中引入。

有三种类型的生成器。

一个 *列表生成器* 具有以下语法:

Pattern <- ListExpr

其中 ListExpr 是一个求值为项列表的表达式。

一个 *位串生成器* 具有以下语法:

BitstringPattern <= BitStringExpr

其中 BitStringExpr 是一个求值为位串的表达式。

一个 *映射生成器* 具有以下语法:

KeyPattern := ValuePattern <- MapExpression

其中 MapExpr 是一个求值为映射的表达式,或者是通过调用 maps:iterator/1maps:iterator/2 获取的映射迭代器。

一个 *过滤器* 是一个求值为 truefalse 的表达式。

生成器模式中的变量会遮蔽先前绑定的变量,包括在之前的生成器模式中绑定的变量。

在生成器表达式中绑定的变量在表达式外部不可见。

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

**列表推导式** 返回一个列表,其中列表元素是对每个生成器元素的组合(所有过滤器都为真)求值 Expr 的结果。

**位串推导式** 返回一个位串,该位串是通过将对每个位串生成器元素的组合(所有过滤器都为真)求值 BitStringExpr 的结果进行连接而创建的。

**映射推导式** 返回一个映射,其中映射元素是对每个生成器元素的组合(所有过滤器都为真)求值 KeyExprValueExpr 的结果。如果键表达式不唯一,则最后一次出现的值将存储在映射中。

示例

将列表中的每个元素乘以 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]

守卫序列

一个 *守卫序列* 是一系列由分号 (;) 分隔的守卫。如果至少有一个守卫为真,则守卫序列为真。(其余的守卫(如果有)则不会进行求值。)

Guard1; ...; GuardK

一个 *守卫* 是一系列由逗号 (,) 分隔的守卫表达式。如果所有守卫表达式都求值为 true,则该守卫为真。

GuardExpr1, ..., GuardExprN

守卫表达式

有效的保护表达式集合是有效 Erlang 表达式集合的子集。限制有效表达式集合的原因是必须保证保护表达式的评估没有副作用。有效的保护表达式如下:

  • 变量
  • 常量(原子、整数、浮点数、列表、元组、记录、二进制和映射)
  • 构造原子、整数、浮点数、列表、元组、记录、二进制和映射的表达式
  • 更新映射的表达式
  • 记录表达式 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: '<'