2014/3/27

erlang - distributed programming

分散式程式

為什麼要開發分散式程式

  1. 效能:將程式不同的部份,指派給不同的電腦運算
  2. 可靠度:容錯
  3. 規模:服務更多的使用者
  4. 本質分散的應用:網路遊戲或聊天這些天生就分散的應用

分散式模型

  1. 分散式 erlang:以erlang 的 process 為基礎,寫出來的程式,不同 process 運作的程式,可以將 process 分配到不同機器節點上,程式不需要修改。
  2. 以 socket 為基礎的分散式應用:分散式erlang必須要在信賴的區域網路內運作,在非信賴的公用網路上,必須要以 TCP/IP 網路為基礎,撰寫網路分散式程式

開發的步驟

  1. 在單一erlang 節點中,撰寫並測試程式
  2. 在同一台電腦,兩個 erlang 節點中,測試程式
  3. 在兩台電腦的 erlang 節點中,測試程式
  4. 兩台電腦在不同 domain,不同區域網路上,測試程式

最後一個步驟是很重要的,在遠端網路的環境中運作程式,就需要一併確認防火牆是不是有開放連線。

Sample: key-value cache server

在單一erlang 節點中,撰寫並測試程式

server 的程式界面規格

  1. @spec kvs:start() -> true
    啟動 server,註冊名稱為 kvs
  2. @spec kvs:store(Key, Value) -> true
    儲存 Key - Value
  3. @spec kvs:lookup(Key) -> {ok, Value} | undefined
    尋找 key

因為這只是一個簡單的範例,所以是用 process dictionary 做出來的,使用 process dictionary 要注意以下條件:盡量不要使用 process dictionary,可能會導致一些 bug,難以除錯,只有一個地方可以使用,就是拿來儲存「只寫入一次」的變數,如果一個 key 得到的一個值僅此一次,且不會再變動,那麼就可以放入 process dictionary。

-module(kvs).
-export([start/0, store/2, lookup/1]).

start() -> register(kvs, spawn(fun() -> loop() end)).

store(Key, Value) -> rpc({store, Key, Value}).

lookup(Key) -> rpc({lookup, Key}).

rpc(Q) ->
    kvs ! {self(), Q},
    receive
        {kvs, Reply} ->
            Reply
    end.

loop() ->
    receive
        {From, {store, Key, Value}} ->
            put(Key, {ok, Value}),
            From ! {kvs, true},
            loop();
        {From, {lookup, Key}} ->
            From ! {kvs, get(Key)},
            loop()
    end.

測試

1> kvs:start().
true
2> kvs:store({location, joe}, "Island").
true
3> kvs:store(weather, raining).
true
4> kvs:lookup(weather).
{ok,raining}
5> kvs:lookup({location, joe}).
{ok,"Island"}
6> kvs:lookup({location, mary}).
undefined
7> kvs:store(weather, sunny).
true
8> kvs:lookup(weather).
{ok,sunny}

在同一台電腦,兩個 erlang 節點中,測試程式

以 erl -sname kvs_server 啟動 kvs_server 節點

>erl -sname kvs_server
Eshell V5.10.4  (abort with ^G)
(kvs_server@yaoclNB)1> kvs:start().
true

以 erl -sname kvs_client 啟動 kvs_client 節點,然後用 rpc:call(kvs_server@yaoclNB, kvs, store, [weather, fine]) 進行遠端呼叫。

>erl -sname kvs_client
Eshell V5.10.4  (abort with ^G)
(kvs_client@yaoclNB)1> rpc:call(kvs_server@yaoclNB, kvs, store, [weather, fine]).
true
(kvs_client@yaoclNB)2> rpc:call(kvs_server@yaoclNB, kvs, lookup, [weather]).
{ok,fine}

在 kvs_server 節點,kvs:lookup 確實可以查詢出,遠端呼叫填寫進去的 weather - fine 的資料。

(kvs_server@yaoclNB)2> kvs:lookup(weather).
{ok,fine}

在 LAN 的兩台電腦的 erlang 節點中,測試程式

以 erl -name kvs_server -setcookie abc 啟動 kvs_server 節點,因為要遠端使用,所以是以 -name 要求 erlang 以長的網域名稱建立此 erlang 節點。-setcookie 是 erlang 節點之間的安全檢查,當此 cookie 值不同時,這兩個 erlang 節點就無法互相進行遠端呼叫。

[root@git temp]# erl -name kvs_server -setcookie abc
Erlang R16B03 (erts-5.10.4) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(kvs_server@git.maxkit.com.tw)1>

如果執行上面的指令後產生以下這個錯誤訊息。

{error_logger,{{2014,1,27},{15,3,21}},"Can't set long node name!\nPlease check your configuration\n",[]}

這時候,就要去檢查機器的 hostname,此 hostname 必須要包含網域資料,這樣才能正確以長的網域名稱建立此 erlang 節點。

以 CentOS 來說,必須要修改 /etc/sysconfig/network 檔案,並以 hostname 指令設定。

>vi /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=git.maxkit.com.tw
GATEWAY=192.168.1.1
>hostname git.maxkit.com.tw

完整的測試過程如下

kvs_server 的部份

[root@git temp]# erl -name kvs_server -setcookie abc
Erlang R16B03 (erts-5.10.4) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(kvs_server@git.maxkit.com.tw)1> kvs:start().
true

kvs_client 的部份

[root@koko temp]# erl -name kvs_client -setcookie abc
Erlang R16B03 (erts-5.10.4) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(kvs_client@koko.maxkit.com.tw)1> rpc:call('kvs_server@git.maxkit.com.tw', kvs, store, [weather, fine]).
true
(kvs_client@koko.maxkit.com.tw)2> rpc:call('kvs_server@git.maxkit.com.tw', kvs, lookup, [weather]).
{ok,fine}

-setcookie 是 erlang 節點之間的安全檢查,當此 cookie 值不同時,這兩個 erlang 節點就無法互相進行遠端呼叫。

kvs_server 的 cookie 為 abc

[root@git temp]# erl -name kvs_server -setcookie abc
Erlang R16B03 (erts-5.10.4) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(kvs_server@git.maxkit.com.tw)1> kvs:start().
true
(kvs_server@git.maxkit.com.tw)2>
=ERROR REPORT==== 27-Jan-2014::15:16:20 ===
** Connection attempt from disallowed node 'kvs_client@koko.maxkit.com.tw' **

kvs_client 的 cookie 為 def,在遠端呼叫 rpc:call 時,就會得到 {badrpc,nodedown} 的錯誤訊息,且 kvs_server 會出現 dissallowed 連線的錯誤訊息。

[root@koko temp]# erl -name kvs_client -setcookie def
Erlang R16B03 (erts-5.10.4) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(kvs_client@koko.maxkit.com.tw)1> rpc:call('kvs_server@git.maxkit.com.tw', kvs, store, [weather, fine]).
{badrpc,nodedown}

在兩個遠端遠端機器中,進行 erlang 分散式運算,有下列步驟。

  1. 啟動 erlang 時要使用 -name,如果是單一機器,則使用 -sname 短機器名稱就可以了。
  2. 確認兩個 erlang nodes 有相同的 cookie,使用 -setcookie。erl 預設會讀取 $HOME/.erlang_cookie 檔案,作為預設的 cookie value,因此在單一機器上,運作兩個 nodes,就不需要加上 -setcookie,直接修改 .erlang_cookie 檔案內容,也可以讓兩個 erlang nodes 環境 cookie 一樣。
  3. 確認 fully qualified hostname 可以被 DNS 解析,如果不能修改 DNS entries,則直接修改機器上的 hosts 檔案,CentOS 為 /etc/hosts,Windows 則在 c:\windows\system32\hosts
  4. 確認兩個系統有相同的程式碼版本,有幾種方式可達到此要求
    (a) 自行複製 kvs.erl
    (b) NFS 共享的磁碟
    (c) 設定程式碼伺服器:erl_prim_loader 模組
    (d) 使用 nl(Mod) 命令,這會在所有連結的電腦上載入 Mod 模組,但前提是要先用 net_admin:ping(Node) 的方式,將 erlang 節點連結在一起

兩台電腦在不同 domain,不同區域網路上,測試程式

基本上就跟在 LAN 的兩台電腦的 erlang 節點中一樣,但因為區域網路之間各有防火牆保護,要進行分散式 erlang 就要採取下列步驟。

  1. 網路要開放 TCP 與 UDP Port 4369,因為 epmd(erlang port mapper daemon) 會使用到這兩個 port
  2. 選擇一個 port 或是一個範圍的 port,進行分散式 erlang,確保這些 port 是開放的。如果這個 port 範圍是 Min 與 Max,就用以下指令啟動 erlang node
    >erl -name ... -setcookie ... -kernel inet_dist_listen_min Min inet_dist_listen_max Max
    如果只有開放一個 Port,就讓上面指令中的 Min=Max 即可。

分散式應用的相關函數

erlang node是一個有自己的位址空間,自己的行程集合,完整的vm的系統。

每個節點都有一個單一的 cookie,在一組 erlang node 之間,cookie 必須要一樣。互連且有相同 cookie 的節點,集合起來就是一個 erlang cluster。

分散式應用的相關BIF如下

  1. @spec spawn(Node, Fun) -> Pid
    在 Node 節點上生成 process
  2. @spec spawn(Node, Mod, Fun, ArgList) -> Pid
    在 Node 節點上生成 process
  3. @spec spawn_link(Node, Fun) -> Pid
  4. @spec spawn_link(Node, Mod, Fun, ArgList) -> Pid
  5. @spec disconnect_node(Node) -> bool() | ignored
    強迫節點斷線
  6. @spec monitor_node(Node, Flag) -> true
    當 Flag 為 true,會打開監控功能,如果 Node 加入或離開「連接的 erlang 節點」集合時,負責估算此 BIF 的行程,將會收到 {nodeup, Node} 與 {nodedown, Node} 訊息
  7. @spec node() -> Node
    本地節點的名稱,如果不是分散式節點,會傳回 nonode@nohost
  8. @spec node(Arg) -> Node
    Arg 為 PID、Ref或Port,會傳回 Arg 所在的節點
  9. @spec nodes() -> [Node]
    傳出連結的所有其他節點的 list
  10. @spec is_alive() -> bool()
    如果是系統的一部分,就傳出 true
  11. {RegName, Node}!Msg
    會送出 Msg 到 Node 節點註冊的行程 RegName

分散式應用相關的函式庫

  1. rpc
    call(Node, Mod, Fun, ArgList) -> Result | {badrpc, Reason}
  2. global
    提供註冊分散式系統名稱與鎖的函數

遠端生成的範例

-module(dist_demo).
-export([rpc/4, start/1]).

start(Node) ->
    spawn(Node, fun() -> loop() end).

rpc(ServerPid, M, F, A) ->
    ServerPid ! {rpc, self(), M, F, A},
    receive
        {SenderPid, Response} ->
            Response
    end.

loop() ->
    receive
        {rpc, SenderPid, M, F, A} ->
            SenderPid ! {self(), (catch apply(M, F, A))},
            loop()
    end.

測試
啟動 server 節點

[root@git temp]# erl -name server -setcookie abc
Erlang R16B03 (erts-5.10.4) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(server@git.maxkit.com.tw)1>

啟動 client 節點

[root@koko temp]# erl -name client -setcookie abc
Erlang R16B03 (erts-5.10.4) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(client@koko.maxkit.com.tw)1>

在 client 節點呼叫遠端生成程式,產生 server 節點的 process,並投過訊息傳遞,遠端估算 erlang:node(),然後取得結果。

(client@koko.maxkit.com.tw)1> Pid = dist_demo:start('server@git.maxkit.com.tw').
<6016.43.0>
(client@koko.maxkit.com.tw)2> dist_demo:rpc(Pid, erlang, node, []).
'server@git.maxkit.com.tw'
(client@koko.maxkit.com.tw)3>

設定 erlang cookie 的方法

  1. 將相同的 cookie 值儲存到 $HOME/.erlang.cookie
  2. 啟動 erl 時,以 -setcookie 指定
  3. erlang:set_cookie(node(), C)

以 socket 為基礎的分散式應用

分散式 erlang 適合寫「彼此信任」的叢集應用,不適合開放式的「不信任」環境。當我們有所有機器的控制權,我們可以用分散式 erlang 統一控管所有的機器。

當不同人管理不同機器,想控制哪些程式可以在他們的機器執行時,就要使用受限制版本的 spawn。

lib_chan

lib_chan 是作者 Joe Armstrong 提供的一個 module,可讓使用者手動控制那個行程可以在這個機器上生成。

lib_chan 需要自己編譯,請到 書本的網頁 取得原始程式碼,需要的程式有 lib_chan, lib_chan_auth, lib_chan_cs, lib_chan_mm, lib_md5。

  1. @spec start_server() -> true
    在本地主機上,啟動一個伺服器,該伺服器的行為由 $HOME/.erlang/lib_chan.conf 決定
  2. @spec start_server(Conf) -> true
    在本地主機上,啟動一個伺服器,該伺服器的行為由 Conf 決定
    configuration file 內容包含下列兩項
    (a) {port, NNNN}:對 port number: NNNN 進行監聽
    (b) {service, S, password, P, mfa, SomeMod, SomeFunc, SomeArgsS}:定義一個服務 S,密碼為 P,如果服務開始,就會以 SomeMod:SomeFunc(MM, ArgsC, SomeArgsS) 建立行程,處理來自客戶端的訊息。MM 是一個 proxy 行程的 PID,此代理行程用來傳送訊息給客戶端,參數 ArgsC 是來自客戶端的連線呼叫

  3. @spec connect(Host, Port, S, P, ArgsC) -> {ok, Pid} | {error, Why}
    對主機 Host 開啟 Port,啟動密碼為 P 的伺服器 S,如果密碼正確,會傳回 {ok, Pid},此 Pid 為「負責送訊息到伺服器」的代理行程PID

當客戶端呼叫 connect/5,會產生兩個 proxy process,一個在客戶端,一個在伺服器端。proxy process 負責處理 TCP 封包資料轉換,攔截控制行程的離開訊號,關閉 socket。

範例

configuration file: conf

%% conf
{port, 1234}.
{service, nameServer, password, "ABXy45", mfa, mod_name_server, start_me_up, notUsed}.

當客戶端呼叫
connect(Host, 1234, nameServer, "ABXy45", nil)
伺服器就會生成
mod_name_server:start_me_up(MM, nil, notUsed)
其中 MM 是 proxy process 的 PID,使用它來對客戶端溝通

%% mod_name_server.erl
-module(mod_name_server).
-export([start_me_up/3]).

start_me_up(MM, _ArgsC, _ArgS) ->
    loop(MM).

loop(MM) ->
    receive
        {chan, MM, {store, K, V}} ->
            kvs:store(K, V),
            loop(MM);
        {chan, MM, {lookup, K}} ->
            MM ! {send, kvs:lookup(K)},
            loop(MM);
        {chan_closed, MM} ->
            true
    end.

mod_name_server 遵循下面的協定:

  1. 如果客戶端送出訊息 {send, X} 給伺服器,它會出現在 mod_name_server,訊息格式類似 {chan, MM, X} (MM 是伺服器 proxy process 的 PID)
  2. 如果客戶端終結或用來溝通的 socket 關閉,伺服器會收到 {chan_closed, MM} 的訊息
  3. 如果伺服器想送訊息 X 到客戶端,它會呼叫 MM!{send, X}
  4. 如果伺服器想手動關閉連線,可執行 MM!close

此協定為 middle-man protocol,客戶端與伺服器端都要遵守

測試

1> kvs:start().
true
2> lib_chan:start_server().
lib_chan starting:"lib_chan.conf"
ConfigData=[{port,1234},
            {service,nameServer,password,"ABXy45",mfa,mod_name_server,start_me_up,notUsed}
true

在另一個 erl

1> {ok, Pid} = lib_chan:connect("localhost", 1234, nameServer, "ABXy45", "").
{ok,<0.41.0>}
2> lib_chan:cast(Pid, {store, joe, "writing a book"}).
{send,{store,joe,"writing a book"}}
3> lib_chan:rpc(Pid, {lookup, joe}).
{ok,"writing a book"}
4> lib_chan:rpc(Pid, {lookup, jim}).
undefined

參考

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

2014/3/21

erlang currency errors

三個新概念:link、exit signal、system process。

process link

當 process A 依賴 process B,就需要對 B 的健康狀態保持注意。要達到此目的有兩種作法,使用 link BIF 或是使用 monitor。

當 A 呼叫 link(B) 時,兩個 process 就會互相監控,如果 A死了,B就會收到 exit signal,反之亦然。此機制可作用在單一 node ,也可以用在分散式的 erlang 系統中。

當一個行程收到 exit signal 時,如果沒有做特別的處理,接收者也會離開,但是行程可以攔截這些 exit signal,當一個行程有捕捉離開訊號,就被稱為「系統行程」。

如果連結到系統行程的行程離開,發出了 exit signal,系統行程不會跟著自動終結,反而會接收離開訊號,並作對應的處理。



on_exit 處理器

如果一個行程離開,想要作某些動作,可以寫一個 on_exit(Pid, Fun) 處理器

process_flag(trap_exit, true) 會將生成的行程轉成系統行程,參考 edoc:process_flag
當 trap_exit 設定為 true,process 收到 exit signal 後會轉成訊息 {'EXIT', From, Reason},然後就能被當成一般的訊息處理。
當 trap_exit 設定為 false,process 收到 normail 以外的 exit signal 時,process 就會死亡並將 exit signal 傳送給 linked processes。

on_exit(Pid, Fun) ->
    spawn(fun() ->
                % 會將這個新生成的行程轉成系統行程
                process_flag(trap_exit, true),
                % 將新的 process 連結到另一個 process: Pid
                link(Pid),
                % 行程 Pid 死亡時,此新的行程會收到 exit signal,Pid 是被連結的行程,剛剛才死亡
                receive
                    {'EXIT', Pid, Why} ->
                        Fun(Why)
                end
          end).

測試,因為 F 無法處理 atom,刻意傳送 atom 造成 Pid 死亡。

1> F = fun() ->
        receive
                X -> list_to_atom(X)
        end
        end.
#Fun<erl_eval.20.80484245>
2> Pid = spawn(F).
<0.34.0>
3> lib_misc:on_exit(Pid, fun(Why) -> io:format(" ~p died with:~p~n",[Pid, Why]) end).
<0.36.0>
4> Pid ! hello.
 <0.34.0> died with:{badarg,[{erlang,list_to_atom,[hello],[]}]} hello
5>
=ERROR REPORT==== 24-Jan-2014::11:21:50 ===
Error in process <0.34.0> with exit value: {badarg,[{erlang,list_to_atom,[hello]
,[]}]}

遠端處理 process 錯誤

因為某個行程的錯誤,會發送出來,並交給另一個行程處理,而 erlang 的行程可以運作在多個機器節點上,也就是說,不受機器的限制,而能遠端處理另一台機器節點上的 process 錯誤,這是 erlang 的容錯精神。

錯誤處理的細節

erlang 錯誤處理的三個底層的概念

  1. 連結 link
    link 定義了兩個行程間錯誤遞送的路徑,兩個連結在一起的行程,其中一個死掉時,另一個會收到離開訊息。目前連結到某個行程的所有行程的集合,稱為 link set

  2. 離開訊號 exit signal
    exit signal 是行程死亡前產生的,此訊號會廣播到 link set 的所有行程,此信號包含了一個參數,說明死亡的原因,該原因可能是 erlang 任何一種 term。

    可利用 exit(Reason) 設定原因,或是讓錯誤發生時自動設定。例如除以0的錯誤原因會是 badarith

    當一個行程成功地估算它的函數,正常把工作做完,就會以 normal 的原因死亡。

    行程 Pid1 可以藉由 exit(Pid2, X) 主動送出一個 exit signal 給 Pid2,但 Pid1 並沒有真的死亡。Pid2將會收到 {'EXIT, Pid1, X'} 的訊息,就像是 Pid1 死亡產生了離開的訊息一樣。因此 Pid1 可以假裝自己已經死亡了。

  3. 系統行程 system process
    當行程收到非正常的離開訊號,它也會死亡,除非它是系統行程。

    當 system process 收到來自 Pid 的離開訊號 Why,該訊號會被轉換成 {'EXIT', Pid, Why},並加入到系統行程的 mailbox 中。

    呼叫 process_flag(trap_exit, true) 會將一個正常的行程轉換成 system process

當離開訊號到達行程時,會根據訊號內容與是否為系統行程,而有不同的處理方式:

trap_exit exit signal 動作
true kill 死亡: 對 link set 廣播此 killed 離開訊號
true X 產生 {'EXIT', Pid, X} 到 mailbox
false normal 繼續: 不處理
false kill 死亡: 對 link set 廣播此 killed 離開訊號
false X 死亡: 對 link set 廣播此 X 離開訊號

kill 這個離開的原因,會產生一個無法被捕捉的離開訊號,收到 kill 的行程一定會死亡,不管是不是 system process。

OTP 裡面的 supervisor process 會用這個功能來殺死一些流氓 rogue 行程。

捕捉離開訊號的慣例

  1. 不在意自己建立的行程有沒有死掉
    單純地用 spawn 產生行程
     Pid = spawn(fun() -> ... end)
  2. 如果自己建立的行程死掉了,我也不想活了
    更明確的說法是,如果自己建立的行程,不正常離開時,我也不想活了
    建立行程的process不能被設定為要捕捉離開訊號,且要用 spawn_link 產生行程

     Pid = spawn_link(fun() -> ... end)
  3. 如果自己建立的行程死掉了,我想要處理錯誤
    使用 spawn_link 與 trap_exits

     process_flag(trap_exit, true),
     Pid = spawn_link(fun() -> ... end),
     ...
     loop(...),
    
     loop(State) ->
         receive
             {'EXIT', SomePid, Reason} ->
                 loop(State1);
         end.

捕捉離開訊號的進階資訊



這會開始三個process A,B,C。A連結到 B,B連結到C,A會捕捉 exit signal,且觀察 B 的離開。如果 Bool 為 true,B會捕捉離開訊號。當離開的原因為 M ,C 會死亡。

-module(edemo1).
-export([start/2]).

start(Bool, M) ->
    A = spawn(fun() -> a() end),
    B = spawn(fun() -> b(A, Bool) end),
    C = spawn(fun() -> c(B, M) end),

    % 這是為了要讓 C 死亡時,列印資料出來才加的
    % 正式的程式不應該使用 sleep 來達成同步
    sleep(1000),
    status(b, B),
    status(c, C).

a() ->      
    process_flag(trap_exit, true),
    wait(a).

b(A, Bool) ->
    process_flag(trap_exit, Bool),
    link(A),
    wait(b).

c(B, M) ->
    link(B),
    case M of
        {die, Reason} ->
            exit(Reason);
        {divide, N} ->
            1/N,
            wait(c);
        normal ->
            true
    end.

wait(Prog) ->
    receive
        Any ->
            io:format("Process ~p received ~p~n",[Prog, Any]),
            wait(Prog)
    end.

sleep(T) ->
    receive
    after T -> true
    end.

status(Name, Pid) ->        
    case erlang:is_process_alive(Pid) of
        true ->
            io:format("process ~p (~p) is alive~n", [Name, Pid]);
        false ->
            io:format("process ~p (~p) is dead~n", [Name,Pid])
    end.

測試1:B 是正常行程,C 執行了 exit(abc),B 會死亡,死亡後再廣播,A會收到訊息 {'EXIT',<0.34.0>,abc}。

1> edemo1:start(false, {die, abc}).
Process a received {'EXIT',<0.34.0>,abc}
process b (<0.34.0>) is dead
process c (<0.35.0>) is dead
ok

測試2:B 是正常行程,C 執行了 exit(normal),B 收到 C 正常離開的訊號,沒有死亡

2> edemo1:start(false, {die, normal}).
process b (<0.38.0>) is alive
process c (<0.39.0>) is dead
ok

測試3:B 是正常行程,C 產生了算術錯誤,B 會死亡,然後廣播,A 會收到訊號,並轉換為訊息 {badarith, ...

3> edemo1:start(false, {divide,0}).
Process a received {'EXIT',<0.42.0>,
                       {badarith,
                           [{edemo1,c,2,
                                [{file,
                                     "d:/projectcase/erlang/erlangotp/src/edemo1.erl"},{line,39}]}]}}

=ERROR REPORT==== 24-Jan-2014::15:20:06 ===
Error in process <0.43.0> with exit value: {badarith,[{edemo1,c,2,[{file,"d:/projectcase/erlang/erlangotp/src/edemo1.erl"},{line,39}]}]}

process b (<0.42.0>) is dead
process c (<0.43.0>) is dead
ok

測試4:B 是正常行程,C 執行了 exit(kill),B 會死亡,死亡後再廣播,A會收到訊息 {'EXIT',<0.34.0>,killed}。

4> edemo1:start(false, {die,kill}).
Process a received {'EXIT',<0.46.0>,killed}
process b (<0.46.0>) is dead
process c (<0.47.0>) is dead
ok

測試5:將 B 改成系統行程,重複上面四個測試,B 都會捕捉來自 C 的錯誤

5> edemo1:start(true, {die, abc}).
Process b received {'EXIT',<0.51.0>,abc}
process b (<0.50.0>) is alive
process c (<0.51.0>) is dead
ok
6> edemo1:start(true, {die, normal}).
Process b received {'EXIT',<0.55.0>,normal}
process b (<0.54.0>) is alive
process c (<0.55.0>) is dead
ok
7> edemo1:start(true, {divide,0}).
Process b received {'EXIT',<0.59.0>,
                       {badarith,
                           [{edemo1,c,2,
                                [{file,
                                     "d:/projectcase/erlang/erlangotp/src/edemo1.erl"}, {line,39}]}]}}

=ERROR REPORT==== 24-Jan-2014::15:25:23 ===
Error in process <0.59.0> with exit value: {badarith,[{edemo1,c,2,[{file,"d:/pro
jectcase/erlang/erlangotp/src/edemo1.erl"},{line,39}]}]}

process b (<0.58.0>) is alive
process c (<0.59.0>) is dead
ok
8> edemo1:start(true, {die,kill}).
Process b received {'EXIT',<0.63.0>,kill}
edemo1:start(true, {die,kill}).process b (<0.62.0>) is alive
process c (<0.63.0>) is dead
ok

如果把 c/2 改成下面這樣,以 exit(B,M) 將 B 停止,觀察 A、B、C 的狀況

c(B, M) ->
    process_flag(trap_exit, true),
    link(B),
    exit(B, M),
    wait(c).

測試

1> edemo2:start(false, abc).
Process c received {'EXIT',<0.34.0>,abc}
Process a received {'EXIT',<0.34.0>,abc}
process a (<0.33.0>) is alive
process b (<0.34.0>) is dead
process c (<0.35.0>) is alive
ok
2> edemo2:start(false, normal).
process a (<0.37.0>) is alive
process b (<0.38.0>) is alive
process c (<0.39.0>) is alive
ok
3> edemo2:start(false, kill).
Process c received {'EXIT',<0.42.0>,killed}
Process a received {'EXIT',<0.42.0>,killed}
process a (<0.41.0>) is alive
process b (<0.42.0>) is dead
process c (<0.43.0>) is alive
ok
4> edemo2:start(true,abc).
Process b received {'EXIT',<0.47.0>,abc}
process a (<0.45.0>) is alive
process b (<0.46.0>) is alive
process c (<0.47.0>) is alive
ok
5> edemo2:start(true,normal).
Process b received {'EXIT',<0.51.0>,normal}
process a (<0.49.0>) is alive
process b (<0.50.0>) is alive
process c (<0.51.0>) is alive
ok
6> edemo2:start(true,kill).
Process c received {'EXIT',<0.54.0>,killed}
Process a received {'EXIT',<0.54.0>,killed}
process a (<0.53.0>) is alive
process b (<0.54.0>) is dead
process c (<0.55.0>) is alive
ok

跟錯誤處理相關的函數

  1. @spec spawn_link(Fun) -> Pid
    建立行程時,同時建立兩個 process 的連結,這兩個是不能分割的步驟,所以並不等同於 spawn 之後再 link

  2. @spec process_flag(trap_exit, true)
    可將行程設定為系統行程

  3. @spec link(Pid) -> true
    建立對稱雙向的連結,在 A 上 link(B),跟在 B 上 link(A) 結果是一樣的,如果 Pid 不存在,會產生 noproc 例外,連結關係只會產生一次,不會重複兩次

  4. @spec unlink(Pid) -> true
    將 目前行程 與 Pid 行程 之間的所有連結移除

  5. @spec exit(Why) -> none()
    會造成目前的行程以 Why 原因終結,如果執行此敘述的時候,不在 catch 裡面,那麼此行程就會以 Why 為參數,廣播離開訊號到 link set

  6. @spec exit(Pid, Why) -> true
    送出離開訊號到 Pid ,原因是 Why

  7. @spec erlang:monitor(process, Item) -> MonitorRef
    設定一個監控器,Item 是行程的 Pid 或是 註冊名稱

erlang 的容錯機制

容錯必須至少要有兩台機器,一個失敗,另一個馬上接手。

erlang 的容錯機制就是,一個行程做事,另一個監控,出錯時馬上接手。在分散式erlang中,「做事的行程」和「監控的行程」可以放在不同的機器上。

此模式稱為 worker-supervisor

monitor

因為 link 是雙向的,如果要阻止其中一個死亡,就要設定為 system process,但有時不想這樣處理,就可以使用 monitor。

monitor 是非對稱連結,如果 process A 監控 process B,B 死亡會送 exit signal 給 A ,但 A 死亡,不會送訊號給 B。

keep-alive process

要讓一個 process 一直活著,如果死亡就馬上重新啟動。

keep_alive 會估算 Fun,啟動一個行程,並註冊名稱為 Name,同時會以 on_exit,產生新的 process 監控 Name 行程,如果接收到離開訊號,就會攔截並再次估算 Fun,而 Fun 的內容是再呼叫 keep_alive,重新產生一個 Name 的行程。

keep_alive(Name, Fun) ->
    register(Name, Pid = spawn(Fun)),
    on_exit(Pid, fun(_Why) -> keep_alive(Name, Fun) end).

on_exit(Pid, Fun) ->
    spawn(fun() ->
                process_flag(trap_exit, true),
                link(Pid),
                receive
                    {'EXIT', Pid, Why} ->
                        Fun(Why)
                end
          end).

register 跟 on_exit 之間,可能會因為 race condition 而造成問題。當程式裡面使用到 spawn、spawn_link、register 時,要注意會不會遇到 race condition。

OTP 內建了server、supervision tree 的功能,這些功能不會遇到 race condition,所以要盡量改使用 OTP library。

參考

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

2014/3/16

永無天日 Under the Never Sky - 維若妮卡 羅西 Veronica Rossi

永無天日的英文書名就很特別 Under the Never Sky,故事就是設定在未來的地球上,生活環境早就已經被破壞,人類在從來都沒有出現過的天空下生活,所能看到的,就只有一層濃密的雲霧以間歇出現的閃電流火。

惡劣的生活環境造就了兩種不同的生活方式,一種是選擇逃離現實,進入烏托邦,在密閉城市中,人類以高科技進入虛擬世界,生活在迷幻的美好世界。而在城市外,還有許多部落因應惡劣的環境,而發展出高度的感官力量,也就是異能者,多數人都有單一的異能,有些是靈聽者,有些是靈視者,主角游隼是比較特別的靈嗅及靈視雙異能者。

故事雖然高潮不斷,但讀起來會覺得,有些地方在轉換場景的時候並不順暢,例如炭渣放出異能,把四周的東西燒成焦炭的時候,下一段就突然說,「在不遠處有個餓扁的光頭人影...」,要想一下,才能意會到這裡在說阿游看到的景象。

圍牆外的死亡工廠,有一百萬種死法,如果真有一百萬種死法,那應該讓讀者更深刻地體會到惡劣環境所造成的生存危機。但實際閱讀後,並沒有特別感受到 「一百萬種死法」的感覺,沒有特別描述惡劣的環境,造成了人類生存的問題。我只讀到部落人都發展出了異能,只看到了人與人之間的爭鬥,烏鴉族跟主角間的戰鬥,故事並沒有對環境怎麼造成人類生存的危機大做文章,主角們在移動的過程中,取得食物也沒特別遇到什麼困難。

永無天日 Under the Never Sky 提供了一些資訊,永無天日並不是單行本,而是三部曲的第一本,第二跟第三集中文版,應該會陸續推出吧,另外這本書也推出了很多個語言的版本。

第一本並沒有說明詠嘆調到底為什麼而被刻意放逐到野外,喚醒了自己另一半的異能,可能得繼續往下看二三部曲才知道,第二本是 Through the Ever Night,第三本是 Into the Still Blue,每一本的英文書名都取得很厲害,不過第二部的中文書名「流火風暴」,似乎就沒有很適當的貼近英文書名的主題。

為了打書,還有書本的預告片,這是很特別的,也不知道是不是為了讓故事改編成電影的作準備,這個預告片拍起來很有感覺,應該也會有基本的好票房吧。

讀書心得:永無天日 UNDER THE NEVER SKY
『永無天日 Under The Never Sky』虛幻的美好將帶來不可避免的毀滅。

2014/3/13

R 語言簡介

R語言的專案 R Project,是一個統計、繪圖整合的開發環境,他的語法跟 S-Plus S 語言類似,裡面內建適合處理大量資料點的統計分析工具。

國家高速網路與計算中心提供了完整的 R 語言教學,雖然商業界最常使用的是 SAS,但學術界最普及的是 R 與 S 語言,跨平台的 R 語言互動環境,可讓使用者 step-by-step 進行資料處理,而不像傳統的方式,得把所有統計分析程式做完,然後執行一次產生報表,nchc 的網站甚至提供了 R 雲端計算環境。

這篇文章 In data scientist survey, R is the most-used tool (other than databases) 中,資料處理技術領域 survey 中,有 71% 使用了 SQL 處理 RDB 資料,第二名就是 43% 使用了 R 語言。

資料處理首要條件,就是要把資料永續保存下來,自從1974年發展了 SQL 開始,因為關聯式資料庫數十年的經驗累積,大家根深蒂固的概念就是要把資料儲存在關聯式資料庫中,資料庫操作就是要用 SQL。

拿到了資料,也確定資料不會遺失了,接下來就是使用資料來做統計、資料分析等等,這時候就有取資料跟運算的問題,以傳統的作法當然就還是直接使用 SQL,搭配一個自己習慣的程式語言來存取資料庫,現在 R 語言提供了許多運算函數,還可以直接產生繪圖,這是另一個可快速使用的解決方案。

Apache Spark目標是要取代 Hadoop,所以一開始就聲稱,在記憶體使用比 Hadoop 快 100倍,在 Disk 使用快 10 倍,它已經從 incubator 畢業SparkR 提供 Spark R 語言的API 界面,一方面是 R 語言原本就適合處理大量資料點,另一方面,Spark 就是用來儲存大量資料的,相信透過這個方式的整合,可大幅改善大量資料統計運算領域的解決方案,這應該是 data mining 的領域吧。

以前作 paper 可能就是用 matlab 或是 gnuplot 來繪製圖形,現在有另一個選擇,就是走向 R 語言。

2014/3/9

我們 MbI - Yevgeny Zamyatin 薩米爾欽

「我們」是反烏托邦三大經典的第一本,第二是赫胥黎的美麗新世界,第三是喬治歐威爾的一九八四,自從大學時期閱讀了美麗新世界與一九八四之後,我還記得讀了這兩本書,寫了一篇報告,直到最近我才知道「我們」這第一本經典。

這類反烏托邦的文學,某方面來說,也可以算是一種科幻的小說,科幻小說強調在某種未來科技高度發展的社會中,人類生活在這樣的社會會產生什麼反應,常常都是印證了,科技始終無法改變人性的事實。

「我們」裡面所描述的社會中,科技也是非常進步的,在故事後段發展出了摘除想像力的手術,這些小說,一直在警告人類,要因應人性去發展科技,就如同Jobs所說的,在他個人追求科技發展的經驗中,NeXT 過度追求技術領先,最終以失敗收場,iPhone 與 iPod 的成功是因為發展的過程,以使用者的體驗為第一優先考量,以人性考量駕馭技術發展才是成功的王道。

每個人受教育的過程,除了嬰幼兒期的教育比較重視音樂、美術等美感教育之外,上了國小之後,開始國民教育,就開始了一連串的理性教育,就算是上美術課,也會遇到藝術家的生平與美術史,老師幾乎都不大會教授純粹的美感了,可能是因為美感是無法評量的,美感強調個人體驗,跟眾人之間的美感差距,完全無法量化,只知道你跟我之間不一樣。

數學一直都被視為是一個純粹的理性基礎科學,所有其他的科學,都是以數學為基礎,「我們」的社會,就是個以純粹的數學構築出來的世界。人民以數字編號為名,每個人按照固定的時間表進行課程,詩人以數學造詩,D-503最討厭的就是遇到不存在的無理數√-1。

故事是以D-503的每日札記進行,我們在閱讀的,是D-503從理性到遇到I-330的轉變過程,讀起來的感覺,就像是感冒生病了,卻又要上數學課寫考券一樣,明明告訴自己要理性思考,但卻又一直胡思亂想,腦袋裡面左邊在計算,右邊在放煙火的感覺,故事並不好讀,也許是因為,我想理性分析D-503的札記,但實際上一個慢慢瘋狂的人的日記,怎麼能讓人好好地喘息與思考呢?

D-503接受了摘除想像力的手術,混亂的生活終致平靜、穩定且單純,身為讀者的我們,該如何選擇自己的未來呢?跟著D-503回歸單純而理性 的生活,進入美麗的烏托邦,或是面對現實的世界,處理混亂的一生,要怎麼做,就由大家自己決定。

我們 (小說) wiki
反烏托邦三部曲先行者──虛構共和國的我與《我們》

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