檢視原始碼 資料類型
Erlang 提供多種資料類型,本節將列出這些類型。
請注意,Erlang 沒有使用者定義的類型,只有由 Erlang 項組成的複合類型(資料結構)。這表示任何測試複合類型的函式,通常命名為 is_type/1
,可能會針對與所選表示法一致的項返回 true
。內建類型的對應函式則不會遇到這個問題。
項(Terms)
任何資料類型的資料片段都稱為項。
數字(Number)
數值字面值有兩種:整數和浮點數。除了傳統的表示法之外,還有兩種 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
比較(Comparisons)
整數和浮點數都共用相同的線性順序。也就是說,1
小於 2.4
,3
大於 2.99999
,而 5
等於 5.0
。
當想要比較整數與整數或浮點數與浮點數時,可能會想使用項等價運算子(=:=
、=/=
)或模式比對。這對於每個數字都有不同表示法的整數來說是可行的,但浮點數有一個令人驚訝的邊緣情況,因為後者有兩個零的表示法,項等價運算子和模式比對會將它們視為不同。
如果您希望數值地比較浮點數,請使用常規比較運算子(例如 ==
),並加入要求兩個參數都為浮點數的 guard。
注意
在 OTP 27 之前,項等價運算子有一個錯誤,它們會將
0.0
和-0.0
視為相同的項。對浮點數零進行相等性比較的舊程式碼應遷移為使用等於(==
)運算子搭配is_float/1
guard,並且已新增編譯器警告來提醒。這些警告可以透過寫入+0.0
來消除,這與0.0
相同,但會讓編譯器將比較解釋為有目的地對0.0
進行。請注意,這並不會破壞與 IEEE 754 的相容性,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
浮點數的表示法(Representation of Floating Point Numbers)
當使用浮點數時,您在列印或進行算術運算時可能看不到您期望的結果。這是因為浮點數在以 2 為底的系統中以固定數量的位元表示,而列印的浮點數則以 10 為底的系統表示。Erlang 使用 64 位元的浮點數。以下是此現象的範例:
1> 0.1+0.2.
0.30000000000000004
實數 0.1
和 0.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 不支援 Inf 和 NaN。任何會導致 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
原子(Atom)
原子是一個字面值,一個具有名稱的常數。如果原子不是以小寫字母開頭,或者包含字母數字字元、底線(_
)或 @
以外的字元,則必須以單引號('
)括住。
範例
hello
phone_number
name@node
'Monday'
'phone number'
位元字串和二元資料(Bit Strings and Binaries)
位元字串用於儲存未輸入的記憶體區域。
位元字串使用位元語法表示。
位元數可以被 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
如需更多範例,請參閱程式設計範例。
參考(Reference)
在已連接的節點中,唯一的項。參考是透過呼叫 make_ref/0
BIF 來建立的。is_reference/1
BIF 會測試一個項是否為參考。
範例
1> Ref = make_ref().
#Ref<0.76482849.3801088007.198204>
2> is_reference(Ref).
true
Fun
Fun 是一個函數式物件。Funs 可以建立匿名函數,並將函數本身(而不是它的名稱)作為引數傳遞給其他函數。
範例
1> Fun1 = fun (X) -> X+1 end.
#Fun<erl_eval.6.39074546>
2> Fun1(2).
3
is_function/1
和 is_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 表示式中閱讀更多有關 Funs 的資訊。如需更多範例,請參閱程式設計範例。
埠識別符(Port Identifier)
埠識別符會識別 Erlang 埠。
open_port/2
會傳回一個埠識別符。is_port/1
BIF 會測試一個項是否為埠識別符。
請在埠和埠驅動程式中閱讀更多有關埠的資訊。
Pid
Pid 是程序識別符的縮寫。每個程序都有一個 Pid,用於識別該程序。Pid 在已連線節點上存活的程序中是唯一的。但是,已終止程序的 Pid 在一段時間後可能會被重新用於新程序。
BIF self/0
會傳回呼叫程序的 Pid。當建立新程序時,父程序可以透過傳回值(如同呼叫 spawn/3
BIF 的情況),或透過訊息(如同呼叫 spawn_request/5
BIF 的情況)來取得子程序的 Pid。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
請在程序中閱讀更多有關程序的資訊。
元組(Tuple)
元組是一種具有固定數量項的複合資料類型。
{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
映射(Map)
映射是一種具有可變數量鍵值關聯的複合資料類型。
#{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 中獲得完全支援。
列表(List)
列表是一種複合資料型別,其中包含可變數量的項目。
[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
模組中找到。
字串(String)
字串使用雙引號 (") 括起來,但它在 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)
記錄是一種用於儲存固定數量元素的資料結構。它具有具名字段,類似於 C 中的 struct。但是,記錄並不是真正的資料型別。相反,記錄運算式在編譯期間會轉換為元組運算式。因此,除非採取特殊措施,否則 shell 無法理解記錄運算式。有關詳細資訊,請參閱 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}
請在 記錄 中閱讀更多關於記錄的資訊。更多範例可以在 程式設計範例 中找到。
布林(Boolean)
Erlang 中沒有布林資料型別。相反,原子 true
和 false
用於表示布林值。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
跳脫序列(Escape Sequences)
在字串(使用 "
分隔)、帶引號的原子,以及 ~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 | (水平) Tab (ASCII 碼 9) |
\v | 垂直 Tab (ASCII 碼 11) |
\ XYZ, \ YZ, \ Z | 具有八進位表示法 XYZ、YZ 或 Z 的字元 |
\xXY | 具有十六進位表示法 XY 的字元 |
\x{ X...} | 具有十六進位表示法的字元;X... 是一個或多個十六進位字元 |
\^a ...\^z \^A ...\^Z | 控制 A 到控制 Z |
\^@ | NUL (ASCII 碼 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