Erlang 的 數據類型


0. 數據類型

返回目錄

基礎數據類型:

類型 說明 備註
Number 數字 包括 Integr 和 Float
Integer 整型 屬於Number
Float 浮點型 屬於Number
Atom 原子
Tuple 元組
List 列表
String 字符串
fun 函數型
Union
binary 二進制型
bitstring 二進制串
record 記錄
map 映射組

內部類型:

類型 說明 備註
any()
none()
pid() 進程id
port() 端口
reference()
[]
Atom
binary()
float()
Fun
Integer
Tuple
Union
UserDefined
ref

1. 整數 (integer)

返回目錄

  1. 定義整數,用 base#value 表示2 ~ 36進制數(10進制除外),超出9的數用 abc…xyz 等字符代替。
    語法:
    1> N10 = 15.          %% 十進制
    2> N2 = 2#00101010.   %% 二進制
    3> N8 = 8#123756322.  %% 八進制
    4> N16 = 16#af6bfa23. %% 十六進制
    
  2. 用 ASCII 編碼表示整數或字符,格式:$char
    1> $a.   %% 字符 a 的ASCII 編碼是 97
    2> $1.   %% 字符 1 的ASCII 編碼是 49
    3> $\n.  %% 換行符 的ASCII 編碼是 10
    4> $\^c. %% Ctrl+C 的ASCII 編碼是 3
    

2. 浮點數 (float)

返回目錄

erlang 沒有單精度浮點數,而是採用 IEEE 754-1985格式的64位雙精度浮點數表示。絕對值在10-323到10308範圍內的實數可以用Erlang的浮點數表示。浮點數由五部分組成:

  1. 可選的正負號
  2. 整數部分
  3. 小數點
  4. 小數部分
  5. 可選的指數部分
  • 定義浮點數
    1> F1 = 1.0.
    2> F2 = 3.1415926.
    3> F3 = -2.3e+6.
    4> F4 = 23.56E-27.
    

3. 原子 (atom)

返回目錄

4. 布爾值 (bool)

返回目錄

Erlang沒有單獨的布爾值類型。
原子 true 和 false 具有特殊的含義,可以用來表示布爾值。
如果所有的函數都返回布爾值,就能將它們用於標準庫函數

5. 元組 (tuple)

返回目錄

元組會在聲明時自動創建,不再使用時則被銷燬

  1. 聲明元組

    P1 = {10, 45}. %% 聲明元組
    P2 = {point, 10, 45}. %% 聲明元組,並增加可讀性
    P3 = {person, {name, joe}, {height, 1.82}}. %% 聲明嵌套元組
    
  2. 元組取值

    {point, X, Y} = {point, 10, 45}. %% X爲10,Y爲45
    {point, C, C} = {point, 25, 25}. %% C爲25,如果值不一樣報錯
    {_, {_, Who}, _} = P3. %% Who爲joe。_爲佔位符(匿名變量,值可不同)
    

6. 列表 (list)

返回目錄

列表的第一個元素叫表頭(head),其餘的叫表尾(tail)。注意列表頭可以是任何事物,但列表尾通常仍然是個列表。

  1. 聲明列表

    Drawing = [{square,{10, 10}, 10}, {triang,{15, 10}, {25, 10}, {30, 40}}].
    
  2. 定義列表
    T是個列表,[H|T]也是個列表,頭是H,尾是T。豎線(|)把頭與尾隔開。[]是個空列表。這是“格式正確的”列表

    % 列表
    ThingsToBuy = [{apple, 10}, {pears, 6}, {milk, 3}].
    % 多表頭的列表
    ThingsToBuy1 = [{oranges, 10}, {newspaper, 1} | ThingsToBuy].
    
  3. 提取列表元素
    如果有一個非空列表 L,那麼表達式 [X | Y] = L (X, Y都是未綁定變量)會提取列表頭作爲X,列表尾作爲Y。

    % 列表
    [Buy1 | ThingsToBuy2] = ThingsToBuy1.
    % Buy1 = {oranges, 4}
    % ThingsToBuy2 = [{newspaper,1}, {apples,10}, {pears,6}, {milk,3}]
    [Buy2, Buy3 | ThingsToBuy3] = ThingsToBuy2.
    % Buy2 = {newspaper,1}
    % Buy3 = {apples,10}
    % ThingsToBuy3 = [{pears,6}, {milk,3}]
    
  4. 列表元素操作

    L -- Y % 從列表 L 中移除元素Y
    L ++ [H] % 不要這麼幹 這是很低效的 
    

列表方法

返回目錄

方法 說明
lists:map 遍歷列表
lists:filter 過濾並生成一個新列表
lists:seq 返回一個包含從N到M所有整數的列表
  • 示例
    Even = fun(X) -> (X rem 2) =:= 0 end.
    
    lists:map(Even, [1, 2, 3, 4, 5, 6, 8]). % [false,true,false,true,false,true,true]
    lists:filter(Even, [1, 2, 3, 4, 5, 6, 8]). % [2,4,6,8]
    lists:seq(1,5). % [1,2,3,4,5]
    

列表推導

返回目錄

列表推導(list comprehension)是無需使用fun、map或filter就能創建列表的表達式

  1. 普通形式 [ expression || Pattern <- ListExpr ]
    • expression(表達式):用於生成新的元素
    • Pattern(模式):用於匹配 list 中的各個元素
    • ListExpr(列表):用生成新列表的源列表
    [ 2*X || X <- [1,2,3,4,5] ]. % [2,4,6,8,10]
    map(F,L) -> [F(X) || X <- L]. % 等價於 map(F, [H | T]) -> [F(H) | map(F, T)].
    
  2. 常見形式 [ X || Qualifier1, Qualifier2, …]
    [ bitstring || generator, filter ]
    • generator(生成器):Pattern <- ListExpr

      • ListExpr:必須是能得出列表的表達式
    • bitstring(位串):BitStringPattern <= BitStringExpr

      • BitStringExpr 必須是能得出 bitstring 的表達式
    • filter(過濾器):既可以是判斷函數(返回true或false),也可以是布爾表達式。

      • 列表推導裏的生成器部分起着過濾器的作用
        [ X || {a, X} <- [{a,1}, {b,2}, {c,3}, {a,4}, hello, "wow"]]. % [1,4]
        
    • Quicksort 快速排序

      qsort( [ ] ) -> [ ];
      qsort( [ Pivot | T ] ) ->
          qsort( [ X || X <- T, X < Pivot ] )
          ++ [ Pivot ] ++
          qsort( [ X || X <- T, X>= Pivot ] ).
      
      1. qsort( [23,6,2,9,27,400,78,45,61,82,14] )
        參數 [Povot | T] 產生綁定 Pivot = 23, T = [6,2,9,27,400,78,45,61,82,14]
      2. 將列表 T 分成兩個表,一個包含T裏所有小於Pivot的元素,另一個包含所有大於等於Pivot的元素
        Smaller = [ X || X <- T, X < Pivot ]. = [6,2,9,14]
        Bigger = [ X || X <- T, X >= Pivot ]. = [27,400,78,45,61,82]
      3. 排序 Smaller 和 Bigger,並將它們與 Pivot 合併
        qsort( [6,2,9,14] ) ++ [23] ++ [27,400,78,45,61,82]
        = [2,6,9,14] ++ [23] ++ [27,45,61,78,82,400]
        = [2,6,9,14,23,27,45,61,78,82,400]
      1> qsort([23,6,2,9,27,400,78,45,61,82,14]). % [2,6,9,14,23,27,45,61,78,82,400]
      
    • 畢達哥拉斯三元數組

      pythag(N) ->
          [ {A,B,C} ||
              A <- lists:seq(1, N), % A 提取列表[1,..N]裏元素
              B <- lists:seq(1, N), % B 提取列表[1,..N]裏元素
              C <- lists:seq(1, N), % C 提取列表[1,..N]裏元素
              A+B+C =< N, % 條件1:A 加 B 加 C 小於等於 N
              A*A+B*B =:= C*C % 條件2:並且 A方 加 B方 等於 C方
          ].
      
  • 迴文構詞
    perms( [ ] ) -> [ [ ] ];
    perms( L ) -> [ [ H | T ] || H <- L, T <- perms( L -- [ H ] ) ],
    
    1. 從 L 裏 提取 H,然後從 perms( L – [H] )裏提取 T,最後返回 [H | T]
    2. H <- L 相當於一個for循環,在每次循環中又執行一次 T <- perms( L – [ H ] )
    3. 相當於循環嵌套,又相當於遞歸
  • 構造自然順序的列表
    1. 總是向列表頭添加元素。
    2. 從輸入列表的頭部提取元素,然後添加到輸出列表的頭部,形成的結果與輸入列表順序相反的輸出列表。
    3. 如果順序很重要,調用lists:reverse/1函數(高度優化過)
    4. 避免違反以上的建議。

7. 字符串 (string)

返回目錄

  1. 定義字符串
    1> Name = "Kate".
    "Kate"
    
    % 在 shell 中,如果列表內的所有整數都代表可打印字符
    % 那麼就會將其打印成字符串字面量。
    3> [83, 117, 114, 112, 114, 105, 115, 101]. % "Surprise"
    % 否則,打印成列表記法
    4> [1, 83, 117, 114, 112, 114, 105, 115, 101].
    % [1,83,117,114,112,114,105,115,101]
    
  2. $語法 知道字符的ASCII碼
    I = $s. %115
    [I-32, $u, $r, $p, $r, $i, $s, $e]. % "Surprise"
    
  3. 輸出數字或Unicode字符
    % 輸出 可打印整數
    X = [97,98,99]. % "abc"
    io:format("~w~n",["abc"]). % [97,98,99]
    % 輸出 Unicode字符
    X1 = "a\x{221e}b". % [97,8734,98]
    io:format("~ts~n", [X1]). % V10.5 版本中顯示爲 a\x{221E}b
    
  4. 字符集
    • 從Erlang的R16B版開始,Erlang源代碼文件都假定採用UTF-8字符集編碼。在這之前用的是ISO-8859-1(Latin-1)字符集。這就意味着所有UTF-8可打印字符都能在源代碼文件裏使用,無需使用任何轉義序列。
    • Erlang內部沒有字符數據類型。字符串其實並不存在,而是由整數列表來表示。用整數列表表示Unicode字符串是毫無問題的。
  5. 轉義字符
    轉義字符 含義 整數編碼
    \b 退格符 8
    \d 刪除符 127
    \e 換行符 27
    \f 換頁符 12
    \n 換行符 10
    \r 回車符 13
    \s 空格符 32
    \t 水平製表符 9
    \v 垂直製表符 11
    \x{…} 十六進制字符
    ^a…^z 或 ^A…^Z Ctrl+A 至 Ctrl+Z 1 至 36
    \’ 單引號 39
    \" 雙引號 34
    \\ 反斜槓 92
    \C C的 ASCII編碼(C是一個字符) (一個整數)

嚴格來說,Erlang 裏沒有字符串。要在 Erlang 裏表示字符串,可以選擇一個由整數組成的列表或者一個二進制型。當字符串表示爲一個整數列表時,列表裏的每個元素都代表了一個 Unicode 代碼點(codepoint)。

映射組(map)

返回目錄

鍵-值對的關聯性集合
鍵可以是任意的Erlang數據類型
比元組佔用更多的存儲空間,查找起來也更慢

  1. 映射組適合以下的情形:

    • 當鍵不能預先知道時用來表示鍵-值數據結構;
    • 當存在大量不同的鍵時用來表示數據;
    • 當方便使用很重要而效率無關緊要時作爲萬能的數據結構使用;
    • 用作“自解釋型”的數據結構,也就是說,用戶容易從鍵名猜出值的含義;
    • 用來表示鍵-值解析樹,例如XML或配置文件;
    • 用JSON來和其他編程語言通信。
    • 鍵和值可以是任何有效的Erlang數據類型
    • 映射組在系統內部是作爲有序集合存儲的,打印時總是使用各鍵排序後的順序,與映射組的創建方式無關
  2. 語法

    % 創建映射組
    Map = #{ Key1 => Val1, Key2 => Val2, ... KeyN => ValN }.
    % 更新映射組
    NewMap = OldMap#{K1 := V1, ...,Kn := Vn}.
    
  3. 舉例

    % 創建映射組
    1> F1 = #{ a => 1, b => 2 }.
    #{a => 1,b => 2}
    2> Facts = #{
        {wife, fred} => "Sue",
        {age, fred} => 45,
        {daughter, fred} => "Mary",
        {likes, jim} => ["Man"]
    }.
    % 替換映射組
    3> F2 = F1#{ c => xx }. % 新建鍵值 c
    #{a => 1,b => 2,c => xx}
    4> F3 = F1#{ c := 3 }. % 錯誤,變量 F1 裏沒有 c 這個 key 
    5> F3 = F2#{ c := 6 }. % ok,F3 的 c 值爲 6
    % 取值
    6> #{ c := X } = F3. % 單字段提取 X = 6
    7> #{ d := Y } = F3. % 錯誤,匹配不到右邊的值
    8> #{ Z => 1547}. % 錯誤,變量 Z 未綁定
    
  4. 模式匹配映射組字段
    可以在函數的頭部使用包含模式的映射組,前提是映射組裏所有的鍵都是已知的

    % 定義一個 count_characters(Str) 函數
    % 讓它返回一個映射組,內含字符串裏各個字符的出現次數
    % 這個例子運行不起來 以後在考究
    count_characters(Str) ->
        count_characters(Str, #{}).
    
    count_characters([H | T], #{ H => N } = X) ->
        count_characters(T, X#{ H := N+1 });
    count_characters([H | T], X) ->
        count_characters(T, X#{ H => 1 });
    count_characters([], X) ->
        X.
    

    執行

    1> count_characters("hello").
    #{101=>1,104=>1,1-8=>2,111=>1}
    
  5. 方法
    maps:new() -> #{} %返回一個空的映射組
    erlang:is_map(M) %如果M是映射組返回true否則返回false。可以用在關卡測試或函數主體中。
    map:to_list(M) -> [{K1, V1}, … ,{Kn, Vn}] %把映射組M裏的所有鍵和值轉換成一個鍵值列表,鍵值在生成的列表裏嚴格按照升序排列。
    maps:from_list([{K1, V1}, …, {Kn, Vn}]) -> M %把一個包含鍵值對的列表轉換成一個映射組M。如果同樣的鍵不止一次的出現,就使用列表裏第一鍵所關聯的值,後續的值都會被忽略。
    maps:size(Map) -> numberOfEntries %返回映射組裏的條目數量。
    maps:is_key(Key, Map) -> boolean()%如果映射組包含一個鍵未key的項就返回true,否則返回false。
    maps:get(Key, Map) -> val %返回映射組裏與Key關聯的值,否則拋出一個異常錯誤。
    maps:find(Key, Map) -> {ok, Value} | error。%返回映射組與Key關聯的值,否則返回error。
    maps:keys(Map) -> [Key1, …, KeyN] %返回映射組所含的鍵列表,按升序排序
    maps:remove(Key, M) -> M1%返回一個新映射組M1,除了鍵未Key的項(如果有的話)被移除外,其他與M一致。
    maps:without([Key1, …, KeyN], M) -> M1 %返回一個新映射組M1,它是M的複製,但移除了帶有[Key1,…, KeyN]列表裏這些鍵的元素。
    maps:difference(M1, M2) -> M3 %M3是M1的複製,但移除了那些與M2裏的元素具有相同鍵的元素
    %他的行爲類似於下面的定義
    maps:difference(M1, M2) ->
    maps:without(maps:keys(M2), M1).
    6. 映射組排序
    映射組在比較時首先會比大小,然後再按照鍵的排序比較鍵和值。
    如果A和B是映射組,那麼當maps:size(A) < maps:size(B)時A < B。
    如果A和B是大小相同的映射組,那麼當maps:to_list(A) < maps:to_list(B)時A < B。
    舉個例子,A = #{age => 23, person => “jim”}小於B = # {email => “[email protected]”, name => “sue”}。這是因爲A的最小鍵(age)比B的最小鍵(email)更小。
    當映射組與其他Erlang數據類型相比較時,因爲我們認爲映射組比列表或元組“更復雜”,所以映射組總是會大於列表或元組
    映射組可以通過io:format裏的~p選項輸出,並用io:read或file:consult讀取。
    7. 以JSON爲橋樑
    有兩個內置函數可以讓映射組和JSON數據相互轉換

    內置函數 說明
    maps:to_json(Map) -> Bin 把一個映射組轉換成二進制型,它包含用JSON表示的該映射組請。注意,不是所有的映射組都能轉換成JSON數據類型。映射組裏所有的值都必須是能用JSON表示的對象。例如,值不能包含的對象有fun、進程標識符和引用等。如果有任何的鍵或值不能用JSON表示,maps:to_json就會失敗。
    maps:from_json(Bin) -> Map 把一個包含JSON數據的二進制型轉換成映射組。
    maps:safe_from_json(Bin) -> Map 把一個包含JSON數據的二進制型轉換成映射組。Bin裏的任何原子必須在調用此內置函數前就已存在,否則就會拋出一個異常錯誤。這樣做是爲了防止創建大量的新原子。出於效率的原因,Erlang不會垃圾回收(garbage collect)原子,所以連續不斷地添加新原子會(在很長一段時間後)讓Erlang虛擬機崩潰。

    上面兩種定義裏的Map都必須是json_map()類型的實例
    - type json_map() = [{json_key(), json_value()}].
    - type json_key() = atom() | binary() | io_list() 並且
    - type json_value() = integer() | binary() | float() | atom() | [json_value] | json_map()
    JSON對象與Erlang值的映射關係如下。
    - SON的數字用Erlang的整數或浮點數表示。
    - JSON的字符串用Erlang的二進制型表示。
    - JSON的列表用Erlang的列表表示。
    - JSON的true和false用Erlang的原子true和false表示。
    - JSON的對象用Erlang的映射組表示,但是有限制:映射組裏的鍵必須是原子、字符串或二進制型,而值必須可以用JSON的數據類型表示。

    當來迴轉換JSON數據類型時,應當注意一些特定的轉換限制。Erlang對整數提供了無限的精度。所以,Erlang會很自然地把映射組裏的某個大數轉換成JSON數據裏的大數,而解碼此JSON數據的程序不一定能理解它。

記錄 (record)

返回目錄

記錄其實就是元組的另一種形式
使用記錄,可以給元組裏的各個元素關聯一個名稱

  1. 應該在下列情形裏使用記錄:
    • 當你可以用一些預先確定且數量固定的原子來表示數據時;
    • 當記錄裏的元素數量和元素名稱不會隨時間而改變時;
    • 當存儲空間是個問題時,典型的案例是你有一大堆元組,並且每個元組都有相同的結構。
  2. 語法,不是shell命令,只能用在源代碼模塊裏使用。
    記錄能保存在 .erl 文件(源代碼)和 .hrl 文件(包含文件,類似c語言的 .h 頭文件)
    -record(Name, {
        % 以下兩個鍵帶有默認值
        key1 = Default1,
        key2 = Default2,
        ...
        % key3 = undefined
        key3,
        ...
    }).
    
  3. 舉例,
    1. 定義記錄。記錄一旦被定義,就可以創建該記錄的實例了
      % records.hrl
      -record(todo, {status = reminder, who = joe, text}).
      
    2. 創建和更新記錄
      % 在 shell 裏讀取 包含記錄的 .hrl 文件
      1> rr("records.hrl").
      [todo]
      % 創建記錄,所有的鍵都是原子,而且必須與記錄定義裏所用的一致
      2> #todo{}.
      #todo{status = reminder, who = joe, text=undefined}
      3> X1 = #todo{ status = urgent, text = "Fix errata in book"}.
      #todo{status = urgent, who = joe, text=undefined}
      % 創建的是原始記錄的副本
      4> X2 = #todo{ status = done }.
      #todo{status = done, who = joe, text = "Fix errata in book"}
      % 提取記錄字段
      5> #todo{who = W, test = Txt} = X2.
      #todo{status = done, who = joe, text = "Fix errata in book"}
      6> W.
      joe
      7> Txt.
      "Fix errata in book"
      % 只提取單個字段,用 . 語法
      8> X2#todo.text
      "Fix errata in book"
      
      1. 在函數裏模式匹配記錄
      clear_status(#todo{status=S, who=W} = R) ->
          % 在此函數內部, S 和 W 綁定了記錄裏的字段值
          % R是整個記錄
          R#todo{status=finished}
          % ...
      
      1. 要匹配某個類型的記錄
      do_something(X) when is_record(X, todo) ->
          %%  ...
      
      1. 忘掉記錄的定義,變換元組
      9> X2
      #todo{status=done,who=joe,text="Fix errata in book"}
      10> rf(todo). % 忘掉記錄todo的定義
      11> X2.
      {todo,done,joe,"Fix errata in book"}
      

函數(fun)

返回目錄

  1. 在模塊裏定義函數
     % test.erl 求三角形第三邊長度
     hypot(X, Y) -> % 定義 命名函數 
         Double = fun(Z) -> 2*Z end, % 定義 匿名函數
         Double(5), % 調用 匿名函數
         math:sqrt(X*X + Y*Y). % 最後一句是函數的返回值
    
  2. 在 shell 裏定義函數
    1> Double = fun(X) -> 2*X end. % 定義 匿名函數
    2> Double(5). % 調用 匿名函數
    
  3. 函數重載
    % test.erl 轉換華氏度與攝氏度
    % 定義參數個數相同,但值不同的函數
    TempConvert = fun({c, C}) -> {f, 32+ C*9/5};  
                                     ({f, F}) -> {c, (F-32)*5/9} % 省略了fun
                               end.
    % 調用
    TempConvert({c, 100}). % {f,212.0}
    TempConvert({f, 212}). % {c,100.0}
    
  4. 函數參數
    % 定義以 fun 作爲參數的函數,跟正常函數一樣
    Test = fun(F) -> F(X) end.
    % 用 匿名函數 當參數
    lists:map(fun(X) -> 2*X end,  [1, 2, 3, 4]). % [2,4,6,8]
    
  5. 返回函數
    % 定義返回值爲 fun 的函數
    Fruit = [apple, pear, orange].
    % fun() -> ( 返回值 ) end. 把 返回值 替換成 函數 即可
    MakeTest = fun(L) -> (fun(X) -> lists.member(X, L) end) end.
    IsFruit = MakeTest(Fruit). % 把返回的函數賦值給 IsFruit
    IsFruit(pear). % true
    IsFruit(apple). % true
    IsFruit(dog). % false
    lists:filter(IsFruit, [dog, orange, cat, apple, bear]). % [orange, apple]
    
  6. 實現功能
    1. 內置函數遍歷列表
      lists:map(fun(X) -> 2*X end,  [1, 2, 3, 4]). % [2,4,6,8]
      
    2. 定義抽象對象(實現 for 循環)
      % lib_misc.erl
      for(Max, Max, F) -> [F(Max)];
      for(I, Max, F) -> [F(I) | for(I+1, Max, F)].
      % other_file.erl
      lib_misc:for(1, 10, fun(I) -> I end). % [1,2,3,4,5,6,7,8,9,10]
      lib_misc:for(1, 10, fun(I) -> I*I end). % [1,4,9,16,25,36,49,64,81,100]
      
    3. 重入解析代碼(reentrant parsing code)
    4. 解析組合器(parser combinator)
    5. 惰性求值器(lazy evaluator)
  7. 定義列表處理函數
    % mylists.erl
    sum( [H | T] ) -> H + sum(T); % 遞歸求和
    sum( [] ) -> 0. % 如果是空 list 的時候,返回 0
    
    map(_, []) -> [];
    map(F, [H|T]) -> [F(H)|map(F,T)].
    % other.erl
    mylists:sum([1, 3, 10]). % 14
    hello:map(fun(X) -> 2*X end, [1,2,3,4,5]). % [2,4,6,8,10]
    
  8. 計算總和
    % 文件 shop.erl
    -module(shop).
    -export([cost/1]).
    
    cost(oranges) -> 5;
    cost(newspaper) -> 8;
    cost(apple) -> 2;
    cost(pears) -> 9;
    cost(milk) -> 7;
    
    % 文件 shop1.erl
    -module(shop1).
    -export([totle/1]).
    
    total([{What, N} | T]) -> shop:cost(What) * N + total(T);
    total([]) -> 0.
    
    執行 shop1.erl
    $ erl
    1> c(shop).
    {ok,shop}
    2> c(shop1).
    {ok,shop1}
    3> shop1:total([]).
    0
    4> shop1.total([{milk, 3}]).
    21
    
    語句匹配所得值爲:What = milk,N = 3,T = []
    執行語句 shop:cost(What) * N + total(T)。
    即:shop:cost(milk) => 7 * 3 + total([]) => 0 = 21
    5> shop1:total([{pears, 6}, {milk, 3}]).
    75
    
    語句匹配所得值爲:What = pears,N = 6,T = [{milk, 3}]
    6> Buy = [{oranges, 4}, {newspaper, 1}, {apple, 10}, {pears, 6}, {milk, 3}].
    7> shop1:total(Buy).
    123
    
  9. 歸集器
    % lib_misc.erl
    % 不推薦,這樣會遍歷2次列表
    odds_and_evens1(L) ->
        Odds = [X || X <- L, (X rem 2) =:= 1],
        Evens = [X || X <- L, (X rem 2) =:= 0],
        {Odds, Evens}.
    % 推薦,只遍歷列表1次,把奇偶數分別添加到適合的列表裏。
    odds_and_evens2(L) -> odds_and_evens_acc(L, [], []).
    odds_and_evens_acc([H|T], Odds, Evens) ->
        case (H rem 2) fo
            1 -> odds_and_evens_acc(T, [H|Odds], Evens);
            0 -> odds_and_evens_acc(T, Odds,  [H|Evens]);
        end;
    odds_and_evens_acc([], Odds, Evens) ->
        { lists:reverse(Odds), lists:reverse(Evens) }. % 得出得結果是反得,需要逆轉
    
    執行
    1> lib_misc:odds_and_evens1([1,2,3,4,5,6]).
    {[1,3,5], [2,4,6]}
    2> lib_misc:odds_and_evens2([1,2,3,4,5,6]).
    {[1,3,5], [2,4,6]}
    
  10. apply
    此內置函數能調用某個模塊裏的某個函數,並向它傳遞參數
    與直接調用函數的區別在於模塊名和/或函數名可以是動態計算得出的
    儘量避免使用 apply
    apply(Mod, Func, [Arg1, Arg2, ..., ArgN])動態調用
    Mod:Func(Arg1, Arg2, ..., ArgN)效果相同,靜態調用
    1. 所有的Erlang內置函數也可以通過apply進行調用,
      1> apply(erlang, atom_to_list, [hello]).
      
    2. Mod 參數不必是一個原子,也可以是一個元組
      {Mod, P1, P2, ..., Pn}:Func(A1,A2,...An)等同於
      Mod:Func(A1,A2,...,An,{Mod,P1,P2,...,Pn})
  11. 元數
    一個函數的元數(arity)是該函數所擁有的參數數量。
    同一模塊裏的兩個名稱相同、元數不同的函數是完全不同的函數。
    Erlang程序員經常將名稱相同、元數不同的函數作爲輔助函數使用。
    % 元數爲 1
    sum(L) -> sum(L, 0). % 累加列表 L 裏的所有元素
    % 元數爲 2 輔助函數,不導出來隱藏它們
    sum([], N) -> N;
    sum([H|T], N) -> sum(T, H+N).
    
函數 說明
Lambda 函數 匿名函數
高階函數 操作其它函數的函數

二進制型與位語法

返回目錄

二進制型(binary)是一種數據結構,它被設計成用一種節省空間的方式來保存大批量的原始數據。
Erlang虛擬機對二進制型的輸入、輸出和消息傳遞都做了優化,十分高效。
如果要保存大批量的無結構數據內容,二進制型應當是首選,比如大型字符串或文件的內容。
二進制型裏的位數都會是8的整數倍,因此對應一個字節串。如果位數不是8的整數倍,就稱這段數據爲位串(bitstring)

二進制型

返回目錄

  1. 二進制型的編寫和打印形式是雙小於號與雙大於號之間的一列整數或字符串
  2. 在二進制型裏使用整數時,它們必須屬於0至255這個範圍。
  3. 二進制型<<“cat”>>是<<99,97,116>>的簡寫形式,二進制型是由字符串裏這些字符的ASCII編碼組成的。
  4. 和字符串類似,如果某個二進制型的內容是可打印的字符串,shell就會將這個二進制型打印成字符串,否則就打印成一列整數。
  5. 可以用內置函數來構建二進制型或提取它裏面的元素,也可以使用位語法
  • 創建二進制型
    1> <<5, 10, 20>>
    <<5,10,20>>
    2> <<"hello">>
    <<"hello">>
    3> <<65, 66, 67>>
    <<"ABC">>
    
  • 二進制內置函數
    1. list_to_banary(L) -> B
      返回二進制型,通過把 io 列表(iolist) L 裏的所有元素壓扁後形成的(壓扁就是移除列表裏所有的括號)。
      io列表本身是循環定義的,是指一個列表所包含的元素是0~255的整數、二進制型或者其他io列表。
      1> Bin1 = <<1,2,3>>. %<<1,2,3>>
      2> Bin2 = <<4,5>>. %<<4,5>>
      3> Bin3 = <<6>>. %<<6>>
      4> list_to_binary([Bin1, 1, [2, 3, Bin2], 4|Bin3]).
      <<1,2,3,1,2,3,4,5,4,6>>
      
    2. term_to_binary(Term) -> Ext_B 把任何erlang數據類型轉換成二進制型。
    3. binary_to_term(Bin) -> Term 將二進制型轉回來
    4. byte_size(Bin) -> Size 返回二進制型的字節數
    5. split_binary(Bin,Pos) -> {B1,B2} 在 pos 處把二進制型 Bin 一分爲二

位語法

返回目錄

位語法是一種表示法,用於從二進制數據裏提取或加入單獨的位或者位串
編寫底層代碼,以位爲單位打包和解包二進制數據時,位語法是極其有用的
開發位語法是爲了進行協議編程(這是Erlang的強項),以及生成操作二進制數據的高效代碼

  1. 位語法表達式
    <<>>
    <<E1,E2, ..., En>> % 每個 Ei 元素都標識出二進制型或位串裏的一個片段
    Ei = Value |
           Value:Size |
           Value/TypeSpecifierList |
           Value:Size/TypeSpecifierList
    <<Size:4, Data:Size/binary, ...>> % 二進制型裏某個Size的值可以通過之前的模式匹配獲得
    
    • 如果表達式的總位數是8的整數倍,就會構建一個二進制型,否則構建一個位串。
    • 當構建二進制型時,Value 必須是已綁定變量、字符串,或是能得出整數、浮點數或二進制型的表達式。
    • 當被用於模式匹配操作時,Value 可以是綁定或未綁定的變量、整數、字符串、浮點數或二進制型。
    • Size 必須是一個能夠得出整數的表達式。
    • 在模式匹配裏,Size必須是一個整數,或者是值爲整數的已綁定變量
    • 如果Size在模式裏所處的位置需要有一個值,它就必須是已綁定變量
    • Size 的值指明瞭片段的大小。它的默認值取決於不同的數據類型,對整數來說是8,浮點數則是64,如果是二進制型就是該二進制型的大小。在模式匹配裏,默認值只對最後那個元素有效。如果未指定片段的大小,就會採用默認值
    • TypeSpecifierList(類型指定列表)是一個用連字符分隔的列表,形式爲End-Sign-Type-Unit。前面這些項中的任何一個都可以被省略,各個項也可以按任意順序排列。如果省略了某一項,系統就會使用它的默認值。
      • End可以是 big | little | native
        它指定機器的字節順序
        native是指在運行時根據機器的CPU來確定。
        默認值是big,也就是網絡字節順序(network byte order)
        這一項只和從二進制型裏打包和解包整數與浮點數有關。當你在不同字節順序的機器上打包和解包二進制型裏的整數時,應當注意設置正確的字節順序
        編寫位語法表達式時,可能有必要先做些試驗。爲了確定自己沒有弄錯,你可以嘗試下面的shell命令:
        1> {<<16#12345678:32/big>>,<<16#12345678:32/little>>,<<16#12345678:32/native>>,<<16#12345678:32>>}.
        {<<18,52,86,120>>,<<120,86,52,18>>,<<120,86,52,18>>,<<18,52,86,120>>}
        
      • Sign可以是 signed | unsigned
        這個參數只用於模式匹配。默認值是unsigned
      • Type可以是 integer | float | binary | bytes | bitstring | bits | utf8 | utf16 | utf32
        默認值是integer。
      • Unit的寫法是unit:1 | 2 | …256
        integer、float和bitstring的Unit默認值是1,binary則是8。utf8、utf16和utf32 類型無需提供值。
      • 一個片段的總長度是Size x Unit字節。binary類型的片段長度必須是8的整數倍。
  2. 定義
    假設要把三個變量(X、Y和Z)打包進一個16位的內存區域。X應當在結果裏佔據3位,Y應當佔據7位,而Z應當佔據6位。在大多數語言裏這意味着要進行一些麻煩的底層操作,包括位移位(bit shifting)和位掩碼(bit masking)。而在Erlang裏,只需要這麼寫:
    M = <<X:3, Y:7, Z:6>> %M的類型是binary 因爲數據的總長度是16位,可以被8整除
    M = <<X:2, Y:7, Z:6>> %M的類型是bitstring 因爲總位數是15
    
  3. 打包和解包16 位顏色
    假設想要表示一種16位RGB顏色。我們決定給紅色通道分配5位,綠色通道分配6位,剩下的5位則分配給藍色通道。(讓綠色通道多使用1位是因爲人眼對綠光更敏感。)
    1> Red = 2.
    2> Green = 61.
    3> Blue = 20.
    % 打包顏色, 定義一個16位數據的雙字節二進制型
    4> Mem = <<Red:5, Green:6, Blue:5>>.
    <<23,180>>
    % 解包顏色
    5> <<R1:5, G1:6, B1:5>> = Mem.
    
  4. 例子
    1. 尋找MPEG音頻數據裏的同步點
      MPEG音頻數據是由許多的幀組成的。每一幀都有它自己的幀頭,後面跟着音頻信息。由於沒有文件頭,所以原則上可以把一個MPEG文件切成多個片段,而每一段都可以獨立播放。任何讀取MPEG流的軟件都應當能找到頭幀,然後同步MPEG數據。
      MPEG頭以一段11位的幀同步(frame sync)信息開頭,它包含11個連續的1位,後面跟着描述後續數據的信息:AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
      AAAAAAAAAAA 同步字(11位,全爲1)
      BB 2位的MPEG音頻版本ID
      CC 2位的層描述
      D 1位保護位

      知道了從A到M的值,就能計算出一個MPEG幀的總長度
      爲了找到同步點,首先假設我們成功定位到了某個MPEG頭的起始位置,然後試着計算該幀的長度。之後可能會發生以下某一種情況。
      • 假設正確,因此當向前跳過幀長後,會找到另一個MPEG頭。
      • 假設錯誤。可能沒有定位到標記MPEG頭起始位置的位序列(由11個連續的1組成)上,或者因爲字的格式不正確而無法計算幀長。
      • 假設錯誤,但定位到的若干字節音樂數據碰巧和MPEG頭的起始位置很相似。在這種情況下,可以計算幀長,但是當向前跳過這段距離後,我們無法找到新的MPEG頭。
        爲了確保萬無一失,我們將尋找三個連續的MPEG頭。同步的程序如下所示:
    
    
    1. 構建微軟通用對象文件格式(Microsoft Common Object File Format,簡稱COFF)的二進制數據文件。打包和解包二進制數據文件(例如COFF)的典型做法是使用二進制型和二進制模式匹配
    
    
    1. 解包一個IPv4數據報(datagram)
    
    
  5. 位串:處理位級數據
    對位串(bitstring)的模式匹配是位級操作,這樣我們就能在單次操作裏打包和解包位的序列。
    這對編寫需要操作位級數據的代碼來說極其有用,例如沒有按照8位邊界對齊的數據或者可變長度數據,它們的數據長度用位而不是字節來表示。
    1> B1 = <<1:8>>. % <<1>>
    2> byte_size(B1). % 1
    3> is_binary(B1). % true
    4> is_bitstring(B1). % true
    5> B2 = <<1: 17>>. % <<0,0,1:1>>
    6> is_binary(B2). % false
    7> is_bitstring(B2). % true
    8> byte_size(B2). % 3 字節大小
    9> bit_size(B2). % 17 位大小
    
    不能把一個位串寫入文件或套接字(二進制型則可以),因爲文件和套接字使用的單位是字節。
    提取出字節裏各個單獨的位,使用位推導(bit comprehension)位推導遍歷二進制型並生成列表或二進制型。
    1> B = <<16#5f>>. % <<"_">> 包含單個字節的二進制型
    2> [X || <<X:1>> <= B]. % [0,1,0,1,1,1,1,1] 包含字節裏各個位的列表
    3> << <<X>> || <<X:1>> <= B >>. % <<0,1,0,1,1,1,1,1>> 包含這些位的二進制型
    

類型轉換方法

返回目錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章