查看源代码 数据类型

Erlang 提供了多种数据类型,本节将列出这些类型。

请注意,Erlang 没有用户定义的类型,只有由 Erlang 项组成的复合类型(数据结构)。这意味着任何测试复合类型的函数(通常命名为 is_type/1)都可能对与所选表示形式一致的项返回 true。内置类型的对应函数不会出现这种情况。

任何数据类型的一段数据都称为

数字

数字字面量有两种类型:整数浮点数。除了传统的表示法外,还有两种 Erlang 特有的表示法

  • $char
    字符 char 的 ASCII 值或 Unicode 代码点。
  • base#value
    base 为基数的整数,base 必须是 2 到 36 之间的整数。

忽略前导零。单个下划线字符 (_) 可以插入到数字之间作为视觉分隔符。

示例

1> 42.
42
2> -1_234_567_890.
-1234567890
3> $A.
65
4> $\n.
10
5> 2#101.
5
6> 16#1f.
31
7> 16#4865_316F_774F_6C64.
5216630098191412324
8> 2.3.
2.3
9> 2.3e3.
2.3e3
10> 2.3e-3.
0.0023
11> 1_234.333_333
1234.333333
12> 36#helloworld.
1767707668033969

比较

整数和浮点数共享相同的线性顺序。也就是说,1 小于 2.43 大于 2.99999,并且 5 等于 5.0

当想要比较一个整数与另一个整数或一个浮点数与另一个浮点数时,可能会倾向于使用项等价运算符(=:=, =/=)或模式匹配。这对于每个数字都有不同表示形式的整数有效,但浮点数存在一个令人惊讶的极端情况,因为后者对于零有两种表示形式,项等价运算符和模式匹配认为这两种表示形式不同。

如果您希望在数值上比较浮点数,请使用常规比较运算符(例如 ==)并添加需要两个参数都为浮点数的 guard。

注意

在 OTP 27 之前,项等价运算符存在一个错误,它们认为 0.0-0.0 是相同的项。对浮点零进行相等性比较的旧代码应迁移到使用等于运算符 (==) 和 is_float/1 guards,并且已为此添加了编译器警告。可以通过编写 +0.0 来消除这些警告,它与 0.0 相同,但使编译器将比较解释为有意针对 0.0 进行的。

请注意,这会破坏与 IEEE 754 的兼容性,后者规定 0.0-0.0 应该比较相等:当解释为数字时,它们是相等的 (==),当解释为不透明项时,它们是不相等的 (=:=)。

示例:

1> 0.0 =:= +0.0.
true
2> 0.0 =:= -0.0.
false
3> +0.0 =:= -0.0.
false
4> +0.0 == -0.0.
true

浮点数的表示

在使用浮点数时,在打印或进行算术运算时,您可能看不到您期望的结果。这是因为浮点数在基数为 2 的系统中由固定数量的位表示,而打印的浮点数则使用基数为 10 的系统表示。Erlang 使用 64 位浮点数。以下是这种现象的示例

1> 0.1+0.2.
0.30000000000000004

实数 0.10.2 不能精确地表示为浮点数。

1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
{3.602879701896397e16, true,
 3.602879701896397e16, false}.

36028797018963968 可以精确地表示为浮点数值,但 Erlang 的美化打印器将 36028797018963968.0 四舍五入为 3.602879701896397e16 (=36028797018963970.0),因为范围 [36028797018963966.0, 36028797018963972.0] 中的所有值都由 36028797018963968.0 表示。

有关浮点数及其问题的更多信息,请参阅

如果您需要使用精确的十进制小数,例如表示货币,建议使用处理该问题的库,或者以美分代替美元或欧元工作,这样就不需要十进制小数。

另请注意,Erlang 的浮点数与 IEEE 754 浮点数并不完全匹配,因为 Erlang 中不支持 InfNaN。任何会导致 NaN+Inf-Inf 的操作都会引发 badarith 异常。

示例:

1> 1.0 / 0.0.
** exception error: an error occurred when evaluating an arithmetic expression
     in operator  '/'/2
        called as 1.0 / 0.0
2> 0.0 / 0.0.
** exception error: an error occurred when evaluating an arithmetic expression
     in operator  '/'/2
        called as 0.0 / 0.0

原子

原子是一个字面量,一个带有名称的常量。如果原子不以小写字母开头,或者包含字母数字字符、下划线 (_) 或 @ 以外的其他字符,则应将其括在单引号 (') 中。

示例

hello
phone_number
name@node
'Monday'
'phone number'

位串和二进制

位串用于存储未类型化的内存区域。

位串使用位语法表示。

由可以被 8 整除的位数组成的位串称为二进制

示例

1> <<10,20>>.
<<10,20>>
2> <<"ABC">>.
<<"ABC">>
3> <<1:1,0:1>>.
<<2:2>>

is_bitstring/1 BIF 测试项是否为位串,is_binary/1 BIF 测试项是否为二进制。

示例

1> is_bitstring(<<1:1>>).
true
2> is_binary(<<1:1>>).
false
3> is_binary(<<42>>).
true

有关更多示例,请参阅编程示例

引用

在连接的节点中唯一的项。通过调用 make_ref/0 BIF 创建引用。is_reference/1 BIF 测试项是否为引用。

示例

1> Ref = make_ref().
#Ref<0.76482849.3801088007.198204>
2> is_reference(Ref).
true

Fun

Fun 是一个函数对象。Fun 使创建匿名函数并将函数本身(而不是其名称)作为参数传递给其他函数成为可能。

示例

1> Fun1 = fun (X) -> X+1 end.
#Fun<erl_eval.6.39074546>
2> Fun1(2).
3

is_function/1is_function/2 BIF 测试项是否为 fun。

示例:

1> F = fun() -> ok end.
#Fun<erl_eval.43.105768164>
2> is_function(F).
true
3> is_function(F, 0).
true
4> is_function(F, 1).
false

Fun 表达式 中阅读有关 fun 的更多信息。有关更多示例,请参阅 编程示例

端口标识符

端口标识符标识 Erlang 端口。

open_port/2 返回端口标识符。is_port/1 BIF 测试项是否为端口标识符。

端口和端口驱动程序 中阅读有关端口的更多信息。

Pid

Pid 是进程标识符的缩写。每个进程都有一个 Pid,用于标识该进程。Pid 在连接的节点上处于活动状态的进程中是唯一的。但是,终止进程的 Pid 在一段时间后可能会被重用为新进程的 Pid。

BIF self/0 返回调用进程的 Pid。当创建新进程时,父进程将能够通过返回值获取子进程的 Pid,例如在调用 spawn/3 BIF 时,或者通过消息获取子进程的 Pid,例如在调用 spawn_request/5 BIF 时。Pid 通常在向进程发送信号时使用。is_pid/1 BIF 测试项是否为 Pid。

示例

-module(m).
-export([loop/0]).

loop() ->
    receive
        who_are_you ->
            io:format("I am ~p~n", [self()]),
            loop()
    end.

1> P = spawn(m, loop, []).
<0.58.0>
2> P ! who_are_you.
I am <0.58.0>
who_are_you

进程 中阅读有关进程的更多信息。

元组

元组是一种复合数据类型,具有固定数量的项

{Term1,...,TermN}

元组中的每个项 Term 都称为元素。元素的数量称为元组的大小

存在许多用于操作元组的 BIF。

示例

1> P = {adam,24,{july,29}}.
{adam,24,{july,29}}
2> element(1,P).
adam
3> element(3,P).
{july,29}
4> P2 = setelement(2,P,25).
{adam,25,{july,29}}
5> tuple_size(P).
3
6> tuple_size({}).
0
7> is_tuple({a,b,c}).
true

映射

映射是一种复合数据类型,具有可变数量的键值关联

#{Key1 => Value1, ..., KeyN => ValueN}

映射中的每个键值关联都称为关联对。该对的键和值部分称为元素。关联对的数量称为映射的大小

存在许多用于操作映射的 BIF。

示例

1> M1 = #{name => adam, age => 24, date => {july,29}}.
#{age => 24,date => {july,29},name => adam}
2> maps:get(name, M1).
adam
3> maps:get(date, M1).
{july,29}
4> M2 = maps:update(age, 25, M1).
#{age => 25,date => {july,29},name => adam}
5> map_size(M).
3
6> map_size(#{}).
0

可以在 STDLIB 中的模块 maps 中找到映射处理函数的集合。

映射表达式 中阅读有关映射的更多信息。

变更

映射作为 Erlang/OTP R17 中的一项实验性功能引入。它们的功能得到了扩展,并在 Erlang/OTP 18 中得到了完全支持。

列表

列表是一种复合数据类型,具有可变数量的项。

[Term1,...,TermN]

列表中的每个项 Term 都称为元素。元素的数量称为列表的长度

形式上,列表要么是空列表 [],要么由(第一个元素)和(列表的其余部分)组成。也是一个列表。后者可以表示为 [H|T]。上面的符号 [Term1,...,TermN] 等价于列表 [Term1|[...|[TermN|[]]]]

示例

[] 是一个列表,因此
[c|[]] 是一个列表,因此
[b|[c|[]]] 是一个列表,因此
[a|[b|[c|[]]]] 是一个列表,简写为 [a,b,c]

尾部是列表的列表有时称为正确列表。允许尾部不是列表的列表,例如,[a|b]。但是,这种类型的列表很少有实际用途。

示例

1> L1 = [a,2,{c,4}].
[a,2,{c,4}]
2> [H|T] = L1.
[a,2,{c,4}]
3> H.
a
4> T.
[2,{c,4}]
5> L2 = [d|T].
[d,2,{c,4}]
6> length(L1).
3
7> length([]).
0

可以在 STDLIB 中的模块 lists 中找到列表处理函数的集合。

字符串

字符串用双引号 (") 括起来,但不是 Erlang 中的数据类型。相反,字符串 "hello" 是列表 [$h,$e,$l,$l,$o] 的简写,即 [104,101,108,108,111]

两个相邻的字符串字面量被连接成一个。这是在编译中完成的。

示例

"string" "42"

等价于

"string42"

变更

从 Erlang/OTP 27 开始,两个相邻的字符串字面量必须用空格分隔,否则将出现语法错误。这避免了与三引号字符串可能产生的混淆。

字符串也可以写成三引号字符串,它可以缩进多行,以遵循周围代码的缩进。它们也是原样的,即不允许转义序列,因此不需要转义双引号字符。

变更

三引号字符串是在 Erlang/OTP 27 中添加的。在此之前,3 个连续的双引号字符具有不同的含义。在三引号字符串出现之前,绝对没有理由编写这样的字符序列,但是确实存在一些陷阱;请参阅三引号字符串描述末尾的警告

示例,带有原样双引号字符

"""
  Line "1"
  Line "2"
  """

这等效于普通的单引号字符串(也允许换行符)

"Line \"1\"
Line \"2\""

开头和结尾行都包含分隔符:""" 字符。它们之间的行是内容行。开头行的换行符不被视为字符串内容,最后一个内容行上的换行符也不被视为字符串内容。

缩进由结尾行分隔符前的空格字符序列定义。该字符序列将从所有内容行中删除。结尾行分隔符之前只能有空格,否则将其视为内容行。

开头行不允许在分隔符之后有任何空格之外的字符,并且所有内容行都必须以定义的缩进字符序列开头,否则该字符串将出现语法错误。

这是一个更大的示例

X = """
      First line starting with two spaces
    Not escaped: "\t \r \xFF" and """

    """

这对应于普通字符串

X = "  First line starting with two spaces
Not escaped: \"\\t \\r \\xFF\" and \"\"\"
"

可以通过使用更多双引号字符作为分隔符,在内容行的开头编写连续的双引号字符。这是一个恰好包含四个双引号字符的字符串,使用五个双引号字符作为分隔符

"""""
""""
"""""

这些字符串都是空字符串

""
"""
"""
"""

  """

警告

在添加三引号字符串的 Erlang/OTP 27 之前,字符序列 """ 被解释为 "" ",这意味着将空字符串连接到后面的字符串。所有奇数个双引号字符的序列都具有此含义。

任何偶数个双引号字符都被解释为一系列空字符串,它们被连接(到空字符串)。

没有理由编写这样的字符序列。但是如果发生了这种情况,其含义可能随着三引号字符串的引入而改变。

编译器预处理器在 Erlang/OTP 26.1 中进行了修补,以警告 3 个或更多连续的双引号字符。在 Erlang/OTP 26.2 中,这得到了改进,可以警告没有中间空格的相邻字符串字面量,这也涵盖了字符串末尾的相同问题。

如果编译器发出此类警告,请更改此类双引号字符序列,使其每隔一个引号字符后都有一个空格,删除多余的空字符串,或将其编写为一个字符串。这使代码更具可读性,并且在所有版本中都具有相同的含义。

Sigil

Sigil 是字符串字面量的前缀。它不是 Erlang 中的数据类型,而是一种简写符号,指示如何解释字符串字面量。Sigil 主要提供两件事:一种创建 UTF-8 编码二进制字符串的紧凑方法,以及一种编写原样字符串(不必转义 \ 字符)的方法,例如,对于正则表达式很有用。

Sigil 以波浪号字符 (~) 开头,后跟定义 Sigil 类型的名称。

紧随其后的是 Sigil 内容;分隔符之间的字符序列。允许的分隔符是这些开始-结束分隔符对:() [] {} <>,或这些既是开始又是结束分隔符的字符:/ | ' " ` #三引号字符串分隔符也可以使用。

Sigil 内容的字符转义规则取决于 Sigil 类型。当 Sigil 内容为原样时,没有转义字符。Sigil 内容在找到结束分隔符时结束,因此不可能在字符串内容中包含结束分隔符字符。分隔符集相当慷慨,并且在大多数情况下,可以选择一个不在字面字符串内容中的结束分隔符。

三引号字符串分隔符允许在结束分隔符中选择比字符串内容中更多的引号字符,从而即使对于原样字符串,也方便了在行首具有 " 字符序列的任何内容。

Sigil 是

  • ~ - Vanilla(默认)Sigil。UTF-8 编码的 binary/0 的简写。此 Sigil 不会影响字符转义规则,因此使用三引号字符串分隔符时,它们与 ~B 的相同,而对于其他字符串分隔符,它们与 ~b 的相同。

  • ~b - 二进制 Sigil。 UTF-8 编码的 binary() 的简写,如同在 Sigil 内容上调用 unicode:characters_to_binary/1 一样。字符转义规则与 ~s 相同。

  • ~B - 原样二进制 Sigil。与 ~b 相同,但 Sigil 内容是原样的。

  • ~s - 字符串 Sigil。string() 的简写,即 Unicode 代码点的列表 [char()]字符转义规则与普通 string/0 相同。在普通字符串上使用此 Sigil 实际上没有任何作用。

  • ~S - 原样字符串 Sigil。与 ~s 相同,但 Sigil 内容是原样的。在三引号字符串上使用此 Sigil 实际上没有任何作用。

示例

<<"\"\\µA\""/utf8>> = <<$",$\\,194,181,$A,$">> =
    ~b"""
        "\\µA"
        """ = ~b'"\\µA"' =
    ~B"""
        "\µA"
        """ = ~B<"\µA"> =
    ~"""
        "\µA"
        """ = ~"\"\\µA\"" = ~/"\\µA"/
[$",$\\,$µ,$A,$"] =
    ~s"""
        "\\µA"
        """ = ~s"\"\\µA\"" = ~s["\\µA"] =
    ~S"""
        "\µA"
        """ = ~S("\µA") =
    """
        "\µA"
        """ = "\"\\µA\""

相邻的字符串在编译时连接,但这对于 Sigil 是不可能的,因为它们被转换为通常可能不连接的项。因此,"a" "b" 等效于 "ab",但 ~s"a" "b"~s"a" ~s"b" 是语法错误。~s"a" ++ "b" 但是,计算结果为 "ab",因为 ++ 运算符的两个操作数都是字符串。

变更

Sigil 是在 Erlang/OTP 27 中引入的

Record

record 是一种用于存储固定数量元素的数据结构。它具有命名字段,类似于 C 中的 struct。但是,record 不是真正的数据类型。相反,record 表达式在编译期间被转换为元组表达式。因此,除非采取特殊措施,否则 shell 不会理解 record 表达式。有关详细信息,请参阅 STDLIB 中的模块 shell

示例

-module(person).
-export([new/2]).

-record(person, {name, age}).

new(Name, Age) ->
    #person{name=Name, age=Age}.

1> person:new(ernie, 44).
{person,ernie,44}

Record中阅读有关 record 的更多信息。更多示例可以在编程示例中找到。

Boolean

Erlang 中没有 Boolean 数据类型。相反,原子 truefalse 用于表示 Boolean 值。is_boolean/1 BIF 测试一个项是否为布尔值。

示例

1> 2 =< 3.
true
2> true or false.
true
3> is_boolean(true).
true
4> is_boolean(false).
true
5> is_boolean(ok).
false

转义序列

在字符串(" 分隔符)、带引号的原子以及 ~b~s sigil 的内容中,识别以下转义序列

序列描述
\b退格(ASCII 代码 8)
\d删除(ASCII 代码 127)
\e转义(ASCII 代码 27)
\f换页(ASCII 代码 12)
\n换行/新行(ASCII 代码 10)
\r回车(ASCII 代码 13)
\s空格(ASCII 代码 32)
\t(水平)制表符(ASCII 代码 9)
\v垂直制表符(ASCII 代码 11)
\XYZ、\YZ、\Z具有八进制表示 XYZ、YZ 或 Z 的字符
\xXY具有十六进制表示 XY 的字符
\x{X...}具有十六进制表示的字符;X... 是一个或多个十六进制字符
\^a...\^z \^A...\^ZControl A 到 Control Z
\^@NUL (ASCII code 0)
\^[转义(ASCII 代码 27)
\^\文件分隔符(ASCII 代码 28)
\^]组分隔符(ASCII 代码 29)
\^^记录分隔符(ASCII 代码 30)
\^_单元分隔符(ASCII 代码 31)
\^?删除(ASCII 代码 127)
\'单引号
\"双引号
\\反斜杠

表格:已识别的转义序列

变更

从 Erlang/OTP 26 开始,$\^? 的值已更改为 127 (删除),而不是 31。以前的版本允许 $\^ 之后的任何字符;从 Erlang/OTP 26 开始,只允许使用文档中记载的字符。

三引号字符串中,不识别转义序列。唯一不能在三引号字符串中写入的文本是行首(仅以空格开头)的三个连续的双引号字符。可以通过对字符串分隔符使用比字符串中更多的双引号字符来解决此限制。对于开始分隔符,允许任何三个或以上的数字,并且结束分隔符与开始分隔符相同。

当三引号字符串分隔符与~, ~B~S sigil 一起使用时,也适用相同规则,但是对于~b~s sigil ,则使用普通字符串的转义序列,如上所述。

变更

三引号字符串和 sigil 是在 Erlang/OTP 27 中引入的。

类型转换

有许多用于类型转换的 BIF。

示例

1> atom_to_list(hello).
"hello"
2> list_to_atom("hello").
hello
3> binary_to_list(<<"hello">>).
"hello"
4> binary_to_list(<<104,101,108,108,111>>).
"hello"
5> list_to_binary("hello").
<<104,101,108,108,111>>
6> float_to_list(7.0).
"7.00000000000000000000e+00"
7> list_to_float("7.000e+00").
7.0
8> integer_to_list(77).
"77"
9> list_to_integer("77").
77
10> tuple_to_list({a,b,c}).
[a,b,c]
11> list_to_tuple([a,b,c]).
{a,b,c}
12> term_to_binary({a,b,c}).
<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
{a,b,c}
14> binary_to_integer(<<"77">>).
77
15> integer_to_binary(77).
<<"77">>
16> float_to_binary(7.0).
<<"7.00000000000000000000e+00">>
17> binary_to_float(<<"7.000e+00">>).
7.0