8.表达 | 8. Expressions
8表达式
在本节中,列出所有有效的Erlang表达式。在编写Erlang程序时,也允许使用宏和记录表达式。但是,这些表达式在编译过程中被扩展,从这个意义上来说不是真正的Erlang表达式。宏和记录表达式分为几个部分:
Preprocessor
Records
8.1表达评价
除非另有明确说明,否则在表达式本身进行评估之前对所有的子表达式进行评估。例如,考虑以下表达式:
Expr1 + Expr2
Expr1
和Expr2
也是表达式,在执行添加之前首先评估 - 以任何顺序。
许多操作符只能应用于某种类型的参数。例如,算术运算符只能应用于数字。错误类型的参数会导致badarg
运行时错误。
8.2项
最简单的表达形式是一个术语,它是一个整数,浮点数,原子,字符串,列表,映射或元组。返回值是该术语本身。
8.3变量
变量是一个表达式。如果一个变量绑定到一个值,返回值就是这个值。未绑定变量只能在模式中使用。
变量以大写字母或下划线(_)开头。变量可以包含字母数字字符,下划线和@
。
例子:
X
Name1
PhoneNumber
Phone_number
_
_Height
变量绑定到值使用pattern matching
。Erlang使用单一赋值
,也就是说,变量只能绑定一次。
所述匿名变量
由下划线表示(_),并且可以在需要的变量,但其值可以忽略使用。
例子:
[H|_] = [1,2,3]
例如,以下划线(_)开头的_Height
变量是正常变量,而不是匿名变量。但是编译器会忽略它们,因为它们不会为未使用的变量生成任何警告。
例子:
以下代码:
member(_, []) ->
[].
可以被重写为更具可读性:
member(Elem, []) ->
[].
这会导致对未使用变量的警告Elem
,如果代码是使用warn_unused_vars
设置的标志进行编译的。相反,代码可以重写为:
member(_Elem, []) ->
[].
请注意,由于以下划线开头的变量不是匿名的,因此匹配:
{_,_} = {1,2}
但是这失败了:
{_N,_N} = {1,2}
变量的作用域是它的函数子句。在的一个分支结合的变量if
,case
或receive
表达必须在所有的分支被绑定到具有表达之外的值。否则,他们在表达之外被视为“不安全”。
对于try
表达式而言,变量范围是有限的,因此表达式中绑定的变量总是“unsafe”。
8.4模式
模式与术语具有相同的结构,但可以包含未绑定的变量。
例子:
Name1
[H|T]
{error,Reason}
模式被允许在条款头,case
和receive
表达式和匹配表达式。
匹配运算符=in模式
如果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.
模式中的字符串前缀
当匹配字符串时,以下是一个有效的模式:
f("prefix" ++ Str) -> ...
这是等价的语法糖,但难以阅读:
f([$p,$r,$e,$f,$i,$x | Str]) -> ...
模式表达
如果一个算术表达式同时满足以下两个条件,它可以在模式中使用:
- 它只使用数字运算符或按位运算符。
- 它的价值可以计算为一个常数时,遵守。
例子:
case {Value, Result} of
{?THRESHOLD+1, ok} -> ...
8.5匹配
以下匹配Expr1
模式针对Expr2
:
Expr1 = Expr2
如果匹配成功,则模式中的任何未绑定变量都会被绑定,并Expr2
返回值。
如果匹配失败,badmatch
则会发生运行时错误。
例子:
1> {A, B} = {answer, 42}.
{answer,42}
2> A.
answer
3> {C, D} = [1, 2].
** exception error: no match of right-hand side value [1,2]
8.6函数调用
ExprF(Expr1,...,ExprN)
ExprM:ExprF(Expr1,...,ExprN)
在函数调用的第一种形式中ExprM:ExprF(Expr1,...,ExprN)
,每一个ExprM
和ExprF
必须是一个原子或一个表达式,其值为一个原子。据说这个函数是使用完全限定的函数名称
来调用的。这通常被称为远程
或外部函数调用
。
例子:
lists:keysearch(Name, 1, List)
在函数调用的第二种形式中ExprF(Expr1,...,ExprN)
,ExprF
必须是一个原子或评估一个乐趣。
如果ExprF
是原子,则称该函数是通过使用隐式限定的函数名称
来调用的。如果该函数ExprF
是本地定义的,则调用该函数。或者,如果ExprF
从M
模块明确导入,M:ExprF(Expr1,...,ExprN)
则会被调用。如果ExprF
既不是本地声明的也不是明确导入的,ExprF
必须是自动导入的BIF的名称。
例子:
handle(Msg, State)
spawn(m, init, [])
例如
这里ExprF
是一个有趣的:
1> Fun1 = fun(X) -> X+1 end,
Fun1(3).
4
2> fun lists:append/2([1,2], [3,4]).
[1,2,3,4]
3>
请注意,调用本地函数时,使用隐式函数名称或完全限定函数名称是有区别的。后者总是指最新版本的模块。见Compilation and Code Loading
和Function Evaluation
。
本地函数名与自动导入的BIF冲突
如果本地函数的名称与自动导入的BIF名称相同,则语义是隐式限定的函数调用将定向到本地定义的函数,而不是BIF。为了避免混淆,有一个编译器指令可用-compile{no_auto_import,[F/A]}),
这使BIF不被自动导入。在某些情况下,这样的编译指令是强制性的。
警告
在OTP R14A(ERTS版本5.8)之前,对具有与自动导入的BIF同名的函数的隐式限定函数调用总是导致调用BIF。在较新版本的编译器中,调用本地函数。这是为了避免将来添加到这组自动导入的BIF不会默默地改变旧代码的行为。
但是,为避免在使用OTP版本R14A或更高版本编译时旧的(R14前)代码改变了其行为,应用以下限制:如果覆盖在R14A之前的OTP版本中自动导入的BIF的名称(ERTS版本5.8)并在代码中对该函数进行隐式限定的调用时,您需要使用编译器指令显式地删除自动导入,或者用完全限定的函数调用替换该调用。否则,你会得到一个编译错误。看下面的例子:
-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])未
使用该指令,则只要使用隐式限定函数名称在模块中调用该函数,编译器就会发出警告。
8.7 If
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
8.8例
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.
8.9发送
Expr1 ! Expr2
Expr2
将消息的值发送到指定的进程Expr1
。该值Expr2
也是表达式的返回值。
Expr1
必须评估为pid,注册名称(原子)或元组{Name,Node}
。Name
是一个原子,Node
是一个节点名称,也是一个原子。
- 如果
Expr1
评估为名称,但该名称未注册,badarg
则会发生运行时错误。
- 即使pid识别不存在的进程,向pid发送消息也不会失败。
- 分布式消息发送,即如果
Expr1
评估为元组{Name,Node}
(或位于另一个节点的pid),也不会失败。
8.10接收
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
ExprT
是评估为一个整数。允许的最高值是16#FFFFFFFF,即该值必须适合32位。receive..after
其工作原理完全相同receive
,只是如果在ExprT
毫秒内没有匹配的消息到达,则会BodyT
被评估。的返回值BodyT
便成为的返回值receive..after
表达。
例子:
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.
超时值ExprT
有两种特殊情况:
infinity
过程将无限期地等待匹配的消息; 这与不使用超时相同。这对于在运行时计算的超时值很有用。0
如果邮箱中没有匹配的消息,则立即发生超时。
8.11术语比较
Expr1 op Expr2
op | 描述 |
---|---|
== | 等于 |
/= | 不等于 |
= < | 小于或等于 |
< | 少于 |
= | 大于或等于 |
比...更棒 | |
=:= | 完全等于 |
=/= | 完全不等于 |
参数可以是不同的数据类型。定义了以下顺序:
number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string
列表按元素逐个比较。元组按大小排序,具有相同大小的两个元组按元素进行比较。
地图按大小排序,两个大小相同的地图按照升序顺序按键进行比较,然后按键顺序按值进行比较。在地图中,按键顺序整数类型被认为小于浮动类型。
将整数与浮点数进行比较时,精度较低的项将转换为另一项的类型,除非运算符是=:=
或之一=/=
。浮点数比整数更精确,直到浮点数的所有有效数字都在小数点左边。当浮动值大于/小于+/- 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
4> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}.
true
8.12算术表达式
op Expr
Expr1 op Expr2
操作者 | 描述 | 参数类型 |
---|---|---|
+ | Unary + | 数 |
- | Unary - | 数 |
+ | | 数 |
- | | 数 |
* | | 数 |
/ | 浮点分割 | 数 |
BNOT | 一位一位不是 | 整数 |
DIV | 整数划分 | 整数 |
业务 | X / Y的整数余数 | 整数 |
带 | 按位与 | 整数 |
生活 | 按位或 | 整数 |
BXOR | 算术按位XOR | 整数 |
BSL | 算术左移 | 整数 |
BSR | Bitshift的权利 | 整数 |
例子:
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
8.13布尔表达式
op Expr
Expr1 op Expr2
操作者 | 描述 |
---|---|
not | 一元逻辑NOT |
and | 逻辑AND |
or | 逻辑或 |
oxr | 逻辑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
8.14短路表达式
Expr1 orelse Expr2
Expr1 andalso Expr2
Expr2
仅在必要时评估。即,Expr2
仅在以下情况下评估:
Expr1false
在orelse
表达式中评估
Expr1
评估为true
一个andalso
表达式。
返回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),
从Erlang/OTP R13A开始,Expr2
不再需要评估为布尔值。因此,andalso
和orelse
现在是尾递归。例如,以下函数在Erlang/OTP R13A及更高版本中是尾递归的:
all(Pred, [Hd|Tail]) ->
Pred(Hd) andalso all(Pred, Tail
all(_, []) ->
true.
8.15列表操作
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]
警告
复杂度与A -- B
成正比length(A)*length(B)
。也就是说,它变得如果两个很慢A
和B
很长的列表。
8.16地图表达式
创建地图
通过让一个表达式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
与map中的现有密钥相匹配M
,则其关联值将由新值替换V
。在这两种情况下,评估的地图表达式都会返回一个新地图。
如果M
不是类型映射,badmap
则抛出类型的异常。
要仅更新现有值,将使用以下语法:
M#{ K := V }
M
是一个类型映射术语,V
是一个表达式,K
是一个表达式,用于评估现有的键M
。
如果键K
不匹配映射中的任何现有键M
,则会badarg
在运行时触发类型的异常。如果K
映射中存在匹配键M
,则其关联值将被新值替换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
被绑定,它必须匹配与K
in 关联的值M
。
例子:
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
是带有文字或绑定变量的任何表达式。如果所有键都存在于map中M
,则所有变量V1 .. Vn
都与它们各自键的相关值相匹配。
如果匹配条件不符合,则匹配失败,或者:
- 一个
badmatch
例外。这是如果它在匹配运算符的上下文中使用的,如示例中所示。
- 或者导致下一个子句在函数头和case表达式中被测试。
匹配地图只允许:=
作为关联的分隔符。
在匹配中声明键的顺序与此无关。
在匹配和匹配每个与键关联的模式时,允许重复键:
#{ K := V1, K := V2 } = M
将表达式与空映射文字匹配,匹配其类型,但不绑定变量:
#{} = Expr
如果表达式Expr
的类型为map,则该表达式匹配,否则失败并出现异常badmatch
。
匹配语法
函数头中允许将文字作为键进行匹配:
%% 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
模块
map_size/1
在erlang
模块
8.17位语法表达式
<<>>
<<E1,...,En>>
每个元素Ei
指定位串的一个段
。每个元素Ei
都是一个值,后跟一个可选的大小表达式
和一个可选的类型说明符列表
。
Ei = Value |
Value:Size |
Value/TypeSpecifierList |
Value:Size/TypeSpecifierList
用于位串构造,Value
是一个表达式,用于计算整数,浮点数或位串。如果表达式不是单个文字或变量,它将被括在圆括号中。
在位字符串匹配中使用,Value
必须是变量、整数、浮点数或字符串。
注意,例如,使用一个字符串作为in <<"abc">>是句法糖for <<$a,$b,$c>>。
在位串结构中使用,Size
是一个计算为整数的表达式。
在位字符串匹配中使用,Size
必须是整数,或绑定到整数的变量。
Size
以单位指定段的大小(见下文)。默认值取决于类型(见下文):
- 为
integer
现在是8点。
- 为
float
是64。
- 为
binary
和bitstring
它是整个二进制或位字符串。
在匹配中,此默认值仅对最后一个元素有效。匹配中的所有其他位字符串或二进制元素都必须有大小规范。
对于utf8
,utf16
和utf32
类型,Size
不得给予。段的大小由类型和值本身隐含确定。
TypeSpecifierList
是类型说明符的列表,以任何顺序用连字符( - )分隔。缺省值用于任何省略的类型说明符。
Type**
= **
| | | | | | | |
默认是。是一个简写,并且是一个简写。有关类型的更多信息,请参阅下文。**
= **
|
只对匹配和类型是重要的。默认是。**
= **
| |
Native-endian意味着在加载时endianness被解析为big
-endian或little
-endian,这取决于运行Erlang机器的CPU的本机特性。字节序仅事宜时,该类型可以是,,,或integer
float
binary
bytes
bitstring
bits
utf8
utf16
utf32integerbytesbinarybitsbitstringutfSignednesssigned
unsignedintegerunsignedEndiannessbig
little
nativeintegerutf16utf32float
。默认是big
。Unit**
= **
unit:IntegerLiteral
允许的范围是1..256。默认为1,for integer
,float
and bitstring
for 8 for binary
。对于类型,和utf8
,必须没有单位说明符。utf16utf32
Size
乘以单位的值给出了位数。一段类型binary
的大小必须能够被8整除。
注
在构建二进制文件时,如果N
整数段的大小太小而不能包含给定的整数,则整数的最高有效位将被静默丢弃,并且只有N
最低有效位被放入二进制文件中。
utf8
,utf16
和utf32
种类用于指定编码/解码Unicode转换格式
分别应用UTF-8,UTF-16,和UTF-32。
构建utf
类型的段时,Value
必须是范围为0..16#D7FF或16#E000 .... 16#10FFFF的整数。badarg
如果Value
超出允许的范围,构造将失败并发生异常。生成的二进制段的大小取决于类型Value
,或两者:
- 对于
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
如果匹配位置的二进制包含有效的UTF-8序列,则二进制文件中的一段类型匹配1-4个字节。(请参阅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,H/bitstring>> = <<1,17,42:12>>.
<<1,17,1,10:4>>
12> H.
<<17,1,10:4>>
13> <<1024/utf8>>.
<<208,128>>
注意,位字符串模式不能嵌套。
还要注意“ B=<<1>>”被解释为“ B =<<1>>”,这是一个语法错误。正确的方法是在'='之后写一个空格:“ B= <<1>>。
Programming Examples
提供了更多示例。
8.18种有趣的表达方式
fun
[Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
Body1;
...;
[Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
BodyK
end
有趣的表达从关键字开始,以关键字fun
结束end
。在它们之间是一个函数声明,类似于regular function declaration
,除了函数名是可选的并且是一个变量(如果有的话)。
有趣头部的变量影响fun函数子句中围绕有趣表达式的函数名称和阴影变量。有趣的身体绑定的变量是本地的有趣的body。
表达式的返回值是得到的乐趣。
例子:
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
是一个整数。从Erlang/ OTP R15开始,Module
,Name
,并且Arity
也可以是变量。以这种方式定义的乐趣是指在最新
版本的模块中Name
带有arity 的功能。以这种方式定义的乐趣不依赖于定义它的模块的代码。ArityModule
提供了更多示例Programming Examples
。
8.19捕捉和抛出
catch Expr
返回Expr
评估期间发生异常的值。在这种情况下,该例外被捕获。
对于类的异常error
,即运行时错误,{'EXIT',{Reason,Stack}}
将返回。
对于类的例外exit
,就是所谓的代码exit(Term)
,{'EXIT',Term}
返回。
对于类的异常throw
,即被调用的代码throw(Term)
,将Term
被返回。
Reason
取决于发生的错误类型,并且Stack
是最近函数调用的堆栈,请参阅Exit Reasons
。
例子:
1> catch 1+2.
3
2> catch 1+a.
{'EXIT',{badarith,[...]}}
注意,catch
低优先级和catch
子表达式通常需要包含在块表达式或括号中:
3> A = catch 1+2.
** 1: syntax error before: 'catch' **
4> A = (catch 1+2).
3
BIF throw(Any)
可用于从功能非本地返回。它必须在一个catch
返回值中进行评估Any
。
例子:
5> catch throw(hello).
hello
如果throw/1
未在捕获范围内计算,则nocatch
出现运行时错误。
8.20试试
try Exprs
catch
[Class1:]ExceptionPattern1 [when ExceptionGuardSeq1] ->
ExceptionBody1;
[ClassN:]ExceptionPatternN [when ExceptionGuardSeqN] ->
ExceptionBodyN
end
这是对catch
.它使下列方面有可能:
- 区分不同的异常类。
- 选择只处理所需的。
- 将其他人传递给封闭的
try
或者catch
默认的错误处理。
注意,尽管该关键字catch
在所使用的try
表达,不存在catch
所述内表达try
的表达。
它将返回Exprs
(一系列表达式Expr1, ..., ExprN
)的值,除非在评估期间发生异常。在这种情况下,将捕获异常,并将ExceptionPattern
具有正确异常类的模式Class
依次与捕获到的异常进行匹配。省略Class
是简写throw
。如果匹配成功并且可选的保护序列ExceptionGuardSeq
为真,则相应ExceptionBody
的评估将成为返回值。
如果在评估过程中发生异常,Exprs
但是没有ExceptionPattern
将权限Class
与真正的保护序列进行匹配,则将异常传递为Exprs
未包含在try
表达式中。
如果在评估过程中发生异常ExceptionBody
,则不会被捕获。
try
表达式可以具有of
部分:
try Exprs of
Pattern1 [when GuardSeq1] ->
Body1;
...;
PatternN [when GuardSeqN] ->
BodyN
catch
[Class1:]ExceptionPattern1 [when ExceptionGuardSeq1] ->
ExceptionBody1;
...;
[ClassN:]ExceptionPatternN [when ExceptionGuardSeqN] ->
ExceptionBodyN
end
如果Exprs
没有例外的成功评估,Pattern
则按照与case
表达式相同的方式将模式顺序地与结果进行匹配,除非如果匹配失败,try_clause
则会发生运行时错误。
Body
没有发现评估过程中发生的异常。
try
表达式还可以增加一个after
部分,用于清理副作用:
try Exprs of
Pattern1 [when GuardSeq1] ->
Body1;
...;
PatternN [when GuardSeqN] ->
BodyN
catch
[Class1:]ExceptionPattern1 [when ExceptionGuardSeq1] ->
ExceptionBody1;
...;
[ClassN:]ExceptionPatternN [when ExceptionGuardSeqN] ->
ExceptionBodyN
after
AfterBody
end
AfterBody
在任一个Body
或之后进行评估ExceptionBody
,不管是哪一个。评估值AfterBody
丢失; try
表达式的返回值与after
没有的部分相同。
即使评价期间发生异常Body
或ExceptionBody
,AfterBody
进行评价。在这种情况下,异常AfterBody
在评估之后传递,因此try
表达式的异常与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 -> {'EXIT',{Reason,erlang:get_stacktrace()}}
end
8.21括号表达式
(Expr)
operator precedences
例如,在算术表达式中,带括号的表达式可用于覆盖:
1> 1 + 2 * 3.
7
2> (1 + 2) * 3.
9
8.22块表达式
begin
Expr1,
...,
ExprN
end
块表达式提供了一种将表达式序列分组的方式,类似于一个子句体。返回值是最后一个表达式的值ExprN
。
8.23清单理解
列表推导是许多现代函数式编程语言的一个特征。根据一定的规则,它们为列表中的元素提供了简洁的符号。
列表理解类似于Zermelo-Frankel集合理论中的集合理解,在Miranda中被称为ZF表达式。他们是类似于setof
和findall
谓词的Prolog。
列表理解是用以下语法编写的:
[Expr || Qualifier1,...,QualifierN]
这里Expr
是一个任意表达式,每个表达式Qualifier
都是一个生成器或一个过滤器。
- 一个生成器写成: Pattern <- ListExpr。 ListExpr必须是一个表达式,该表达式评估为术语列表。
- 一个
位串发电机
被写为:
BitstringPattern <= BitStringExpr.
BitStringExpr
必须是一个表达式,它的计算结果是一个位串。
滤波器
是一个表达式,计算结果为true
或false
在发电机模式.The变量,函数子句中阴影变量,周围列表comprehensions.A列表理解返回一个列表,其中,所述元件是评估的结果Expr
为发电机的每个组合列表元素和位串生成器元素,所有过滤器均为真。
- 一个
生成器
被写为:
Pattern <- ListExpr.
ListExpr
必须是计算为术语列表的表达式。
- 一个位串发生器写成: BitstringPattern <= BitStringExpr。 BitStringExpr必须是一个评估为位串的表达式。
滤波器
是中的一个表达式true
或false
。
生成器模式中的变量,函数子句中的影子变量,围绕位串综合。
位字符串理解返回一个位字符串,该字符串是通过连接计算结果来创建的。BitString
对于位串生成器元素的每个组合,所有过滤器都是真的。
例子:
1> << << (X*2) >> || <<X>> <= << 1,2,3 >> >>.
<<2,4,6>>
中提供了更多的示例。Programming Examples.
8.25保护序列
后卫序列
是警卫的序列,由分号(;)分隔。如果至少有一名守卫是真的,则守卫顺序为真。(剩下的警卫,如果有的话,没有评估。)
Guard1;...;GuardK
后卫
是保护表达式的序列,由逗号(,)隔开。如果所有警卫表达评估为“守卫”的话true
。
GuardExpr1,...,GuardExprN
有效的守卫表达式集
(有时称为守卫测试)是有效的Erlang表达式集的一个子集。限制这组有效表达式的原因是必须保证对警卫表达式的评估没有副作用。有效的警戒表达如下:
- 原子
true
- 其他常量(术语和绑定变量),都被认为是错误的
- 呼叫表中指定的BIF
Type Test BIFs
- 术语比较
- 算术表达式
- 布尔表达式
- 短路表达式(
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) |
| length(List) |
| map_size(Map) |
| node() |
| node(Pid|Ref|Port) |
| round(Number) |
| self() |
| size(Tuple|Bitstring) |
| tl(List) |
| trunc(Number) |
| tuple_size(Tuple) |
如果算术表达式,布尔表达式,短路表达式或对防护BIF的调用失败(由于参数无效),则整个防护失败。如果守卫是守卫序列的一部分,则评估序列中的下一名守卫(即下一个分号后面的守卫)。
8.26运营商优先权
优先级降低的运营商优先级:
: | |
---|---|
| |
Unary + - bnot not | |
/ * div rem band and | Left associative |
Left associative | |
++ -- | Right associative |
== /= =< < >= > =:= =/= | |
andalso | |
orelse | |
= ! | Right associative |
catch | |
评估表达式时,首先评估具有最高优先级的运算符。具有相同优先级的运营商根据其关联性进行评估。
例子:
左边的关联算术运算符从左到右进行求值:
6 + 5 * 4 - 3 / 2 evaluates to
6 + 20 - 1.5 evaluates to
26 - 1.5 evaluates to
24.5