這些天,一直在鼓搗wxWidgets。裝上了code::blocks,學了bakefile,為的是輔助使用wxErlang。
前期的鋪墊告一段落,正戲開場。
一直以為,學習也要講效率,學以致用,沒用的不學。要真正做到這點,不難,集中精力學習examples就行。
examples集中了知識精華,展示了技術訣竅,是把握關鍵技術的捷徑。
學習examples,全面深入地琢磨程式的運行機制,有助於理解掌握技術細節。
挑選的案例,是erl5.7.1/lib/wx-0.98.1/examples/sudoku。其中有4個erl模組,將挨個詳細解剖分析。
一、模組sudoku.erl
-module(sudoku).
-export([go/0]).
-compile(export_all).
-include("sudoku.hrl").
start() ->
spawn_link(fun() -> init(halt) end).
go() ->
spawn_link(fun() -> init(keep) end).
init(Halt) ->
?TC(sudoku_gui:new(self())),
receive {gfx, GFX} -> ok end,
case sudoku_game:init(GFX) of
Halt -> erlang:halt();
Stop -> exit(Stop)
end.
tc(Fun,Mod,Line) ->
case timer:tc(erlang, apply, [Fun,[]]) of
{_,{'EXIT',Reason}} -> exit(Reason);
{T,R} ->
io:format("~p:~p: Time: ~p/n", [Mod, Line, T]),
R
end.
(1)、編譯指令-export([go/0])和-compile(export_all)
-export([go/0])是說,其他模組可以在源碼級調用本模組的函數go,即:sudoku:go().
-compile(export_all)是說,本模組編譯成.beam後,全部函數均開放調用。
例如,在eshell中,直接調用以下函數都是允許的:
3> sudoku:init(keep).
4> sudoku:tc(fun() -> sudoku_gui:new(self()) end, sudoku, 40).
可是,如果屏蔽指令 %% -compile(export_all),將只能調用 sudoku:go(),否則,會出錯:
10> sudoku:init(keep).
** exception error: undefined function sudoku:init/1
(2)spawn與spawn_link的主要區別
這是並發編程的基本問題。Erlang把建立進程,叫做“spawn”的用意,是為了突出進程的主從關係。“spawn”是孵化的意思,產生的是子進程。
建立進程的函數有2個,spawn與spawn_link。二者的主要區別在於,spawn出的進程意外崩潰後,不影響父進程的運行;spawn_link出的進程意外崩潰後,則連帶父進程一併停運。
(3)GUI子進程的初始化與停運
本模組中有一函數init/1,它掌控著GUI子進程的初始化,和程式進程的停運。這個函數做5件事:
①調用模組sudoku_gui的函數new/1
②監測函數sudoku_gui:new/1的運行用時
③接收來自sudoku_gui的訊息{gfx,GFX}
④調用sudoku_game:init(GFX)
⑤根據sudoku_game:init(GFX)返回的值,確定進程的終止或異常退出
第①和第②這兩件事情,是用“宏” ?TC(sudoku_gui:new(self()))完成的,它是個函數調用的嵌套語句,很有Erlang的特色。
宏TC定義在sudoku.hrl
-define(TC(Cmd), tc(fun() -> Cmd end, ?MODULE, ?LINE)).
把它展開,就是 tc(fun() -> sudoku_gui:new(self()) end, ?MODULE, ?LINE)
函數tc/3是在本模組中定義的,第一參數是個函數,第二參數?MODULE是本模組名字(sudoku),第三參數是該語句所在的行。
可以在函數tc/3中顯示一下,會看清楚:
tc(Fun,Mod,Line) ->
io:fwrite("~ntc(~p,~p,~p)~n",[Fun,Mod,Line]),
……
第③件事情,語句 receive {gfx, GFX} -> ok end,接收來自sudoku_gui的訊息。
為什麼說是來自sudoku_gui的訊息呢?函數調用sudoku_gui:new(self())中的參數self,本進程的Pid,告知對方是為了通訊聯絡。
可用語句io:fwrite("~nself() = ~p~n",[self()])顯示看看Pid的尊容,如:self() = <0.41.0>
第④和第⑤兩件事情,是由一個case結構完成的:
case sudoku_game:init(GFX) of
Halt -> erlang:halt();
Stop -> exit(Stop)
end.
涉及sudoku_gui和sudoku_game兩個模組的事情,以後細聊,這裡只說本模組內的事兒。
這個case主管主進程的終止退出。Halt的值是原子halt或keep,來自本模組的函數start()或go()。
erlang:halt()進程正常終止,exit(Stop)進程異常退出,Stop是異常的資訊提示。
(4)有意思的函數tc/3
這個函數名字的意思是time counter,“計時器”。
這個函數也很能體現Erlang函數編程特色。
timer:tc(erlang, apply, [Fun,[]]),是個高階函數,即把函數當成參數值使用的函數。
apply和Fun都是函數。apply是erlang內建函數,Fun是sudoku_gui:new(self())。
我們做個實驗,看看apply()和timer:tc()的工作情況。
49> W= fun(X)-> io:fwrite("~n~s~n",[X])end.
#Fun<erl_eval.6.13229925>
50> apply(W,["i am a worker"]).
i am a worker
ok
51>
52> timer:tc(erlang, apply, [W,["kjkjkjkjk"]]).
kjkjkjkjk
{100000,ok}
53>
case timer:tc(erlang, apply, [Fun,[]]) of
{_,{'EXIT',Reason}} -> exit(Reason);
{T,R} ->
io:format("~p:~p: Time: ~p/n", [Mod, Line, T]),
R
end.
case中函數Fun執行的兩個結果,{_,{'EXIT',Reason}}是發生異常,將導致退出exit(Reason);
另一結果{T,R},即{Time,Result},正常。