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
- not B1
- B1 and B2
- B1 or B2
- 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}.
有一些事先定義的巨集:
- ?FILE 展開為目前的檔案名稱
- ?MODULE 展開為目前的模組名稱
- ?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 處理
- @spec put(Key, Value) -> OldValue.
加入一個 key, value 到 process dictionary,會傳出該 Key 對應的舊 OldValue,如果沒有舊值,就會傳出 undefined. - @spec get(Key) -> Value.
如果沒有Key,就會傳出 undefined. - @spec get() -> [{Key, Value}].
取得整個 list - @spec get_keys(Value) -> [Key].
取得具有某個值的所有 keys 組成的 list - @spec erase(Key) -> Value.
如果沒有Key,就會傳出 undefined. - @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
有兩種
- Expr1 orelse Expr2
先計算 Expr1。如果 Expr1 結果為 true,則 Expr2 就不會被計算。如果 Expr1 結果為 false,則會繼續計算 Expr2。 - 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
沒有留言:
張貼留言