erlang 裡的 data 通常被稱為 term。以下內容涵蓋了 numbers and arithmetic, binaries and bitstring, atoms, tuples, lists, strings, pid, port, references, fun, comparing terms, record
numbers and arithmetic
integer
整數沒有大小限制,隨便怎麼用都可以。1> 100. 100 2> 1234567890*9876543210*99999999. 1219326298933089578736473100
可以用 2進位 到 36進位表示法,16# 就表示是 16進位。
3> 16#FFff. 65535 4> 2#101010110. 342 5> 36#ZZ112ko. 78305427528
可以用 $ 得到任意字元的數值編碼,ASCII/Latin-1/Unicode 都可以。
6> $9. 57 7> $\n. 10 8> $測試. * 1: illegal character 8> $測. 28204
floating point
採用64位 IEEE 754 double precision,規定一定要以數字開頭,不能以 . 開頭。erlang 沒有 sigle-precision floating point。9> 3.14. 3.14 10> -0.123. -0.123 11> 5.312121e23. 5.312121e23 12> 19029.219021e-11. 1.9029219021e-7
arithmetic
基本的算術為 + - *,如果其中有某一個數值為 floating point,則另一個也會轉換為 floating point 進行運算。除法有兩種 / 跟 div,/ 會回傳 floating point,而 div 會將小數部份截斷,rem 則是取得餘數。13> 2*3.14. 6.28 14> 2*4.0. 8.0 15> 2*4. 8 16> 4/2. 2.0 17> 4/3. 1.3333333333333333 18> 4 div 3. 1 19> 4 rem 3. 1 20> math:sqrt(2). 1.4142135623730951
bit operation
bsl 是將位元資料往左移動,bsr 是往右,還有 band, bor, bxor, bnot。21> 1 bsl 4. 16 22> 1 bsr 4. 0 24> 1 bxor 4. 5 25> 1 bor 4. 5 26> 1 band(bnot 4). 1 27> bnot 4. -5
binaries and bitstring
binaries 就是 a sequence of bytes,也就是 8位元 byte 的序列,bitstring 是廣義的 binaries,長度不必是 8 的整數倍。binaries 是一個包含在 << ... >> 內,用逗號區隔的整數序列,可填入 0~255 的數值,超過時,例如 256 會自動轉換為 0。
28> <<0,1,2,3,255>>.
<<0,1,2,3,255>>
29> <<0,1,2,3,256>>.
<<0,1,2,3,0>>
30> <<0,1,2,3,255,256>>.
<<0,1,2,3,255,0>>
31> <<"hello", 32, "world">>.
<<"hello world">>
32> <<0,1,2,3,255,-256>>.
<<0,1,2,3,255,0>>
33> <<0,1,2,3,255,-2>>.
<<0,1,2,3,255,254>>
atoms
atom 類似 java 的 enum,只要兩個 atom 的字元全部一樣,就代表他們是完全相等的。不需要宣告就可以直接使用,他是由一連串的字元組成的,通常以小寫字母開頭,後面可以使用大小寫字母、數字、底線、@,如果還要用到其他字元,就要用單引號括起來,字元的長度上限為 255,單一系統的 atom 數量上限為 1048576 個。
atom 一旦被建立,除非系統 restart,否則就永遠不會被清除,長期運作的系統,要避免動態生成 atom。
35> ok.
ok
36> error.
error
37> trap_exit.
trap_exit
38> route77.
route77
39> test_atom.
test_atom
40> test@atom.
test@atom
41> '@!#!$@%'.
'@!#!$@%'
42> true.
true
43> false.
false
44> undefined.
undefined
tuples
以 { } 表示,是固定長度,依照順序排列的元素列表,裡面可放0到多個元素,也可以再放入另一個 tuple。 tuple 的第一個元素,通常是以 atom 來標記這個 tuple 存放的資料標記,也稱為 tagged tuple。
45> {1,2,3}.
{1,2,3}
46> {one,two,three}.
{one,two,three}
47> {one,{nested,"two", {string}}}.
{one,{nested,"two",{string}}}
48> {position, 5, 2}.
{position,5,2}
49> {size, 25}.
{size,25}
lists
以 [ ] 表示,可存放任意數量的 terms。
50> [].
[]
51> [1,2,3].
[1,2,3]
52> [[1,2,3], ["test", 4]].
[[1,2,3],["test",4]]
referential transparency
erlang 的 list 必須要遵循引用透明性 referential
transparency,因為 erlang 的變數的值不能修改,X 的 reference 可以傳遞到程式的任何一處,但 X 在每一個地方所參考到的值都是一樣,不會改變。此一特性的優點是:
- 大幅減少程式的錯誤,因為變數不會改變的特性
- 將單一 process 程式重構成多個 processes 時,不需要重寫 code
- 不存在對現有資料結構寫入的 operation,因此可以對內部記憶體與multi-processes處理進行更好的優化
新資料要加在 list 的左邊
為了達到 referential transparency 的緣故,新元素要加在 list 的左邊,元素的加法是用 | 。
兩個 list 的加法是用 ++ ,++ 右邊的 list 並不會被破壞,而是藉由一個 pointer,成為新 list 的一部分。
53> [1|[]].
[1]
54> [2|[1]].
[2,1]
55> [ 4,3,2 | [1] ].
[4,3,2,1]
56> [4,3,2] ++ [1,0].
[4,3,2,1,0]
57> [4] ++ [3,2,1,0].
[4,3,2,1,0]
[4,3,2] ++ [1,0] 依序 會處理 [ 2 | [1,0] ] -> [ 3 | [2,1,0]] -> [ 4 | [3,2,1,0]],因此左邊 list 的長度會決定 ++ 的耗時長短,實務上使用時,左邊的 list 長度要比較短,且要盡量從左邊加入新元素到列表中。
list 的結構
頭元素:head,tail會指向 list 其他的部份,nil 代表一個空 list。
list 通常用來存放臨時資料、中間結果、字串的cache。對於需要長期保存的資料,要改用 binary 來儲存。
(im)proper list
proper list 都是以 nil 作為 list 的結尾,而 [ 1 | ok ] 這種以 atom 作為結尾的 list 就是 improper list。
很多 list 處理的函數通常會以 nil 來作為結束的判斷,而這些函數如果傳入 improper list 就會造成系統 crash,因此要避免使用 improper list,如果在程式中看到了improper list,就代表可能程式寫錯了。
strings
erlang 的雙引號字串實際上就等同於 list,元素內容就是個字元的數值編碼。
erlang shell為了區分 string 與 list 會檢查 list 裡面所有的元素是不是能列印到 console 上,否則就列印為整數list,要強迫讓 shell 列印出實際的內容,可在前面加上 0 ,例如 [0|v(1)]。
1> "and".
"and"
2> "\t\r\n".
"\t\r\n"
3> [97,98,99,100].
"abcd"
4> [32,9,13,10].
" \t\r\n"
5> [$a,$b,$c,$d].
"abcd"
6> [0|v(1)].
[0,97,110,100]
pid, port, references
Pid
所有程式都需要一個 erlang process 才能運作,每一個process都有一個 Pid,shell 會以 <0.32.0> 的格式列印 Pid,在 shell 中可使用 self() 取得目前 process 的 Pid。8> self(). <0.32.0>
Port
Port 跟 Pid 差不多,可跟 Erlang 外界通訊,但不具有程式碼的執行能力,格式為 #Port<0.472> 。Ref
可用 make_ref() 產生一個 Ref,格式為 ,通常備用做各種保證資料唯一性的標籤。9> make_ref(). #Ref<0.0.0.60>
fun
erlang 為 functional programming language,可將函數視為參數傳入另一個函數中,其資料型別為 fun,通常也可稱為 lambda or closure。
comparing terms
erlang 的 terms 可以透過內建的 <, >, == 比較與排序,不同資料型別的排序規則為
numbers < atom < ref < fun < port < pid < tuple < list < strings < binary
可使用 lists:sort 進行排序測試。
12> lists:sort([a,d,3,"test", [2,3],1, c, 3.2, "yy"]).
[1,3,3.2,a,c,d,[2,3],"test","yy"]
小於等於要寫成 =<
大於等於要寫成 >=
完全相等(同一個)要寫成 =:=
完全相等的否定(不是同一個) 要寫成 =/=
相等 ==
不相等 /=
不使用 30 =:= 30.0 ,改採用 30 == 30.0 算術相等來判斷。
13> 30 =:= 30.0.
false
14> 30 =:= 30.
true
15> 30 =/= 30.0.
true
16> 30 == 30.0.
true
99%的情況,要使用 =:=,只有在比較 floating point 與 integer 時,== 才有用。
在很多程式或函式庫中,會看到很多地方應該使用 =:= 但是卻是使用 ==,這不會造成程式的錯誤,因為 == 的引數,不是 floating point 而是 integer,兩者的行為是一樣的。
record
當 tuple 裡的元素變多時,我們就不容易記得,那個元素代表什麼意義,record 可以讓我們把名字跟特定的元素關聯起來,record 的本質就是有標記的tuple。
record 的定義宣告只能出現在 module 中,無法直接在 shell 裡面定義,在 shell 中,我們可以用 rr 的指令將定義讀進 shell 中。
rd(R,D) -- define a record
rf() -- remove all record information
rf(R) -- remove record information about R
rl() -- display all record information
rl(R) -- display record information about R
rr(File) -- read record information from File (wildcards allowed)
rr(F,R) -- read selected record information from file(s)
rr(F,R,O) -- read selected record information with options
record 的定義可以放在 .hrl 的檔案中,或是直接放在 .erl module 裡面,以下為定義的語法,可以直接在定義中,給予某些 key 預設值。
-record (Name, {
key1 = DefaultValue1,
key2 = DefaultValue2,
key3,
...
})
範例
-record(customer, {name="<anonymous>", address, phone}).
-record(todos, {status=reminder, who=joe, text}).
我們可以用 rr 讀取 record 定義,然後再以 # 產生 record,因為單一賦值的限制,我們不能修改 record,但可以根據前一個 record,修改某個欄位後,產生一個新的 record。
(erlangotp@yaoclNB)12> rr("D:/projectcase/erlang/erlangotp/src/records.hrl").
[customer,todo]
(erlangotp@yaoclNB)13> X=#todo{}.
#todo{status = reminder,who = joe,text = undefined}
(erlangotp@yaoclNB)14> X1=#todo{status=urgent, text="fix errata in book"}.
#todo{status = urgent,who = joe,text = "fix errata in book"}
(erlangotp@yaoclNB)15> X2=X1#todo{status=done}.
#todo{status = done,who = joe,text = "fix errata in book"}
(erlangotp@yaoclNB)16> Y1=#customer{}.
#customer{name = "<anonymous>",address = undefined,
phone = undefined}
(erlangotp@yaoclNB)17> Y2=#customer{name="test user", address="HomeTown", phone="987654321"}.
#customer{name = "test user",address = "HomeTown",
phone = "987654321"}
一樣可以用 pattern matching 取出某個欄位的值
(erlangotp@yaoclNB)18> #todo{who=W, text=Text}=X2.
#todo{status = done,who = joe,text = "fix errata in book"}
(erlangotp@yaoclNB)19> W.
joe
(erlangotp@yaoclNB)20> Text.
"fix errata in book"
也可以直接用 . 的語法,取出某個欄位的值
(erlangotp@yaoclNB)21> X2#todo.text.
"fix errata in book"
當我們用 rf 將 record 定義忘記,原本的 record X2,就會變成 tuple,如果再次把 todo 的定義讀進來,X2 又會變回 record。
(erlangotp@yaoclNB)22> rf(todo).
ok
(erlangotp@yaoclNB)23> X2.
{todo,done,joe,"fix errata in book"}
(erlangotp@yaoclNB)24> rr("D:/projectcase/erlang/erlangotp/src/records.hrl").
[customer,todo]
(erlangotp@yaoclNB)25> X2.
#todo{status = done,who = joe,text = "fix errata in book"}
參考
Erlang and OTP in Action
Programming Erlang: Software for a Concurrent World