这些天,一直在鼓捣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},正常。