2014年3月4日

erlang basics - other issues

apply

apply(Mod, Func, [Arg1, Arg2, ..., ArgN]) 是 BIF,可讓我們呼叫一個模組裡的一個函數。相當於呼叫
Mod:Func(Arg1, Arg2, ..., ArgN)

apply 用在當程式裡,Mod 與 Func 都是動態計算出來的時候。

1> apply(erlang, atom_to_list, [hello]).
"hello"

但程式要盡量避免使用 apply,因為使用 apply 時,分析工具無法判斷程式碼,編譯器也無法進行最佳化。

attribute

語法為 -AtomTag(...) ,用來定義一個檔案的某些特性。

predefined attributes

  • -module(modname).
    模組宣告。modname 必須要是 atom,檔案名稱習慣是 modname.erl

  • -import(Mod, [Name1/Arity1, Name2/Arity2, ...]).
    匯入指定 Mod 模組,將具有 Arity1 引數的 Name1 函數,匯入後,呼叫函數就不需要加上模組名稱。

  • -export([Name1/Arity1, Name2/Arity2, ...]).
    匯出 Name1/Arity1, Name2/Arity2 函數,只有被匯出的函數,可以從模組外被呼叫。

  • -compile(Options)
    加入 Options 到編譯器選項中,例如 -compile(export_all) 可將模組中所有函數都匯出

  • -vsn(Version)
    指定模組版本

user-defined attributes

語法如下
-SomeTag(Value).

模組屬性的值,會被編譯到模組中,可以在執行期被取出。每次編譯時,module_info/0 與 module_info/1 都會自動被建立。

範例

-module(attrs).
-vsn(1234).
-author({joe,armstrong}).
-purpose("example of attributes").
-export([fac/1]).

fac(1) -> 1;
fac(N) -> N * fac(N-1).

測試

(erlangotp@yaoclNB)1> attrs:module_info().
[{exports,[{fac,1},{module_info,0},{module_info,1}]},
 {imports,[]},
 {attributes,[{vsn,[1234]},
              {author,[{joe,armstrong}]},
              {purpose,"example of attributes"}]},
 {compile,[{options,[{i,"D:/projectcase/erlang/erlangotp/include"},
                     debug_info,nowarn_export_all,nowarn_export_vars,
                     nowarn_shadow_vars,warn_unused_function,
                     warn_deprecated_function,nowarn_obsolete_guard,
                     nowarn_unused_import,warn_unused_vars,
                     warn_unused_record]},
           {version,"4.9.4"},
           {time,{2014,1,16,9,11,33}},
           {source,"d:/projectcase/erlang/erlangotp/src/attrs.erl"}]}]
(erlangotp@yaoclNB)2> attrs:module_info(attributes).
[{vsn,[1234]},
 {author,[{joe,armstrong}]},
 {purpose,"example of attributes"}]

可以使用 beam_lib 的函數,在不載入模組的條件下,分析模組的內容。

-module(extract).
-export([attribute/2]).

attribute(File, Key) ->
    case beam_lib:chunks(File,[attributes]) of
        {ok, {_Module, [{attributes,L}]}} ->
            case lookup(Key, L) of
                {ok, Val} ->  
                    Val;
                error ->
                    exit(badAttribute)
            end;
        _ -> 
            exit(badFile)
    end.

lookup(Key, [{Key,Val}|_]) -> {ok, Val};
lookup(Key, [_|T])         -> lookup(Key, T);
lookup(_, [])              -> error.

測試

(erlangotp@yaoclNB)8> extract:attribute("D:/projectcase/erlang/erlangotp/ebin/attrs.beam", author).
[{joe,armstrong}]

block expression

使用 block expression 將一連串的 expression 接在一起,此區塊的值,是最後一個 ExprN 的值。

begin
    Expr1,
    ...,
    ExprN
end

boolean expression

有四種可能的 boolean expression

  1. not B1
  2. B1 and B2
  3. B1 or B2
  4. B1 xor B2
1> not true.
false
2> true or false.
true
3> (2>1) or (3>4).
true

character set

erlang R16 就支援 source code 裡面可以寫 unicode 的文字,但是預設還是 ISO-Latin-1,而預定 R17 將會把預設值改為 UTF-8。

epp

erlang 的模組在編譯前,會先由 epp 將 巨集展開,並 include 需要的函數檔。

escape

在 "" 裡面可以使用 \ 用以輸入不能列印的字元。

escape char description 整數碼
\b 後退 8
\d 刪除 127
\e Esc 27
\f 換頁 12
\n 換行 10
\r CR 13
\s space 32
\t tab 9
\v 垂直跳格 11
\NNN \NN \N 八進位字元(N是0..7)
\^a..\^z or \^A..\^Z Ctrl+A to Ctrl+Z 1 to 26
\' 單引號 39
\" 雙引號 34
\ 反斜線 92
\C C 字元的 ASCII code (整數)

測試

1> io:format("~w~n", ["\b\d\e\f\n\r\s\t\v"]).
[8,127,27,12,10,13,32,9,11]
ok
2> io:format("~w~n", ["\123\12\1"]).
[83,10,1]
ok
3> io:format("~w~n", ["\'\"\\"]).
[39,34,92]
ok
4> io:format("~w~n", ["\a\z\A\Z"]).
[97,122,65,90]
ok

expression 表示式 與 expression sequence 表示式序列

「估算後會產生值」的就是表示式。catch、if、try...catch 都是表示式,而 record、module 無法被估算,就不是表示式。

表示式序列 E1,E2,...,En 中間用逗號隔開,通常出現在 -> 後面,整個序列的值是最後一個 En 的值。

函數參考

fun LocalFunc/Arity
fun Mod:RemoteFunc/Arity

範例

%% x1.erl
-module (x1).
-export([square/1]).
square(X) -> X*X.

double(L) -> lists:map(fun square/1, L).

%% x2.erl
-module (x2).

double(L) -> lists:map(fun x1:square/1, L).

include file

習慣上 include file的副檔名為 .hrl。檔名中要包含絕對或相對路徑。

通常用在共用的 record 定義。

範例

-include (FileName).

-include ("include/file.hrl").

在編譯時,可以用
erlc 的 -I 或是 c("src/source.erl", [{i, "include/"} ] ). 的方式,增加搜尋檔案的路徑。

引用含有版本號碼的 library

如果程式依賴別的 erlang lib,必須要知道安裝路徑,把目錄納入 include path,此外,安裝路徑可能有版本號碼,例如 kernel 可能會安裝在 c:\Program files\erl5.10.4\lib\kernel-2.16.4 ,於是 include_lib 會自動將檔案名稱起始處去除版本號碼,成為匹配路徑 kernel/ ,在該路徑下面,尋找 include/file.hrl。

-include_lib (FileName).

-include_lib ("kernel/include/file.hrl").

list operation: ++ and --

++ 與 -- 是 infix operator,用來進行 list 的加法與減法。

A -- B 會將 A 中所有 B 的元素移除,如果 X 在 B 只出現 K 次,則只有 A 裡面的前 K 次 X 會被移除。

範例

1> [1,2,3] ++ [4,5,6].
[1,2,3,4,5,6]
2> A=[a,b,c,1,d,e,1,x,y,1].
[a,b,c,1,d,e,1,x,y,1]
3> A -- [1].
[a,b,c,d,e,1,x,y,1]
4> A -- [1,1].
[a,b,c,d,e,x,y,1]
5> A -- [1,1,1].
[a,b,c,d,e,x,y]
6> A -- [1,1,1,1].
[a,b,c,d,e,x,y]

在 pattern 中也可以使用 ++

f("begin" ++ T) -> ...
f([$b,$e,$g,$i,$n | T]) -> ...

macro 巨集

macro 的定義方式如下

-define (Constant, Replacement).
-define (Func(Var1, Var2, ..., VarN), Replacement).

當 epp 遇到 ?MacroName 時,會進行巨集展開

範例

-define (macro1(X,Y), {a,X,Y}).

foo(A) ->
    ?macro1(A+10, b).

% 會展開成
foo(A) ->
    {a, A+10, b}.

有一些事先定義的巨集:

  1. ?FILE 展開為目前的檔案名稱
  2. ?MODULE 展開為目前的模組名稱
  3. ?LINE 展開為目前的行號

巨集中的控制

-undef(Macro):取消巨集的定義
-ifdef(Macro):只有當巨集定義時,才估算下面的表示式
-ifndef(Macro):只有當巨集沒有被定義時,才估算下面的表示式
-else:可在 ifdef 與 ifndef 後面使用
-endif:作為 ifdef 與 ifndef 的結尾

範例

%% m1.erl
-module(m1).
-export([start/0]).

-ifdef(debug).
-define(TRACE(X), io:format("TRACE ~p:~p ~p~n",[?MODULE, ?LINE, X])).
-else.
-define(TRACE(X), void).
-endif.

start() ->  loop(5).

loop(0) -> 
    void;
loop(N) ->
    ?TRACE(N),
    loop(N-1).

測試

(erlangotp@yaoclNB)6> c("m1",{d,debug}).
{ok,m1}
(erlangotp@yaoclNB)7> m1:start().
TRACE m1:15 5
TRACE m1:15 4
TRACE m1:15 3
TRACE m1:15 2
TRACE m1:15 1
void
(erlangotp@yaoclNB)8> c("m1").
{ok,m1}
(erlangotp@yaoclNB)9> m1:start().
void

pattern 中的巨集運算子

在第三行呼叫 {tag1,A,B} 時,系統會重新建立 {tag1,A,B}

func1([{tag1,A,B}|T]) ->
    ...
    ... f(..., {tag1,A,B}, ...)
    ...

更有效且不會出錯的方法為,把 {tag1,A,B} 指定為暫時變數 Z,然後傳遞給 f

func1([{tag1,A,B}=Z|T]) ->
    ...
    ... f(..., Z, ...)
    ...

兩個 term 也可以用同樣的方式改寫

func1([{tag,{one,A}=Z1,B}=Z2|T]) ->
    ...
    ... f(..., Z2, ...)
    ... g(..., Z1, ...)
    ...

運算子的優先順序

不確定優先順序時,就加上括號。

運算子 組合性
:
#
+, - , bnot, not
/, *, div, rem, band, and 左組合
+, -, bor, bxor, bsl, bsr, or, xor 左組合
++, -- 右組合
==, /=, =<, <, >=, >, =:=, =/=
andalso
orelse

行程字典 process dictionary

每個 process 都有自己的私用資料儲存空間,稱為 process dictionary,這是一個 associative array,也就是 map、hashmap或hashtable。

此字典可以用以下 BIF 處理

  1. @spec put(Key, Value) -> OldValue.
    加入一個 key, value 到 process dictionary,會傳出該 Key 對應的舊 OldValue,如果沒有舊值,就會傳出 undefined.
  2. @spec get(Key) -> Value.
    如果沒有Key,就會傳出 undefined.
  3. @spec get() -> [{Key, Value}].
    取得整個 list
  4. @spec get_keys(Value) -> [Key].
    取得具有某個值的所有 keys 組成的 list
  5. @spec erase(Key) -> Value.
    如果沒有Key,就會傳出 undefined.
  6. @spec erase() -> [{Key, Value}].
    移除整個 process dictionary
1> get().
[]
2> put(x,20).
undefined
3> get(x).
20
4> get(y).
undefined
5> put(y,40).
undefined
6> get(y).
40
7> get().
[{y,40},{x,20}]
8> erase(x).
20
9> get().
[{y,40}]
10> erase().
[{y,40}]
11> get().
[]

注意:盡量不要使用 process dictionary

使用 process dictionary 可能會導致一些 bug,難以除錯,只有一個地方可以使用,就是拿來儲存「只寫入一次」的變數,如果一個 key 得到的一個值僅此一次,且不會再變動,那麼就可以放入 process dictionary。

Ref

Ref 是全域唯一的 erlang term,他是透過 erlang:make_ref() 所建立的。

可用來當作獨特唯一的標籤,包含在資料庫中,之後可以比對是否相等 equality。例如在錯誤追蹤系統終,可為每個錯誤報告產生新的 Ref。

boolean expression shortcut

有兩種

  1. Expr1 orelse Expr2
    先計算 Expr1。如果 Expr1 結果為 true,則 Expr2 就不會被計算。如果 Expr1 結果為 false,則會繼續計算 Expr2。
  2. Expr1 andalso Expr2
    先計算 Expr1。如果 Expr1 結果為 true,則會繼續計算 Expr2。如果 Expr1 結果為 false,則 Expr2 就不會被計算。

注意:在 A or B 與 A and B 中,A 與 B 都一定會被計算。

參考

Erlang and OTP in Action
Programming Erlang: Software for a Concurrent World