2014/1/27

erlang basics - data type

erlang 裡的 data 通常被稱為 term。以下內容涵蓋了 numbers and arithmetic, binaries and bitstring, atoms, tuples, lists, strings, pid, port, references, fun, comparing terms, record

numbers and arithmetic

  1. 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
  2. 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
  3. 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
  4. 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 在每一個地方所參考到的值都是一樣,不會改變。此一特性的優點是:

  1. 大幅減少程式的錯誤,因為變數不會改變的特性
  2. 將單一 process 程式重構成多個 processes 時,不需要重寫 code
  3. 不存在對現有資料結構寫入的 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

  1. Pid
    所有程式都需要一個 erlang process 才能運作,每一個process都有一個 Pid,shell 會以 <0.32.0> 的格式列印 Pid,在 shell 中可使用 self() 取得目前 process 的 Pid。

     8> self().
     <0.32.0>
  2. Port
    Port 跟 Pid 差不多,可跟 Erlang 外界通訊,但不具有程式碼的執行能力,格式為 #Port<0.472> 。

  3. 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