Parameterized modules in Erlang
请参考这篇文章
http://ftp.sunet.se/pub/lang/erlang/workshop/2003/paper/p29-carlsson.pdf
我这里讲述的重点是如何实现的。先看代码:
root@nd-desktop:~/test/m# cat main.erl
% File: main.erl
-module(main).
-export([start/0]).
start() ->
M1 = print:new("Humpty"),
M2 = print:new("Dumpty"),
M1:message("Hello!"),
M2:message("Hi!"),
ok.
root@nd-desktop:~/test/m# cat print.erl
% File: print.erl
-module(print, [Name]).
-export([message/1]).
message(Text) ->
io:fwrite("~s: '~s'~n", [Name, Text]),
ok.
编译运行
root@nd-desktop:~/test/m# erlc *.erl
root@nd-desktop:~/test/m# erl -noshell -s main -s erlang halt
Humpty: 'Hello!'
Dumpty: 'Hi!'
在使用上是模拟面向对象的, 面向对象的同学会感到很亲切。但是erlang如何做到这个魔术的呢?
我们来分析下:
root@nd-desktop:~/test/m# erlc +"'S'" *.erl
root@nd-desktop:~/test/m# cat main.S
{module, main}. %% version = 0
{exports, [{module_info,0},{module_info,1},{start,0}]}.
{attributes, []}.
{labels, 7}.
{function, start, 0, 2}.
{label,1}.
{func_info,{atom,main},{atom,start},0}.
{label,2}.
{allocate_zero,1,0}.
{move,{literal,"Humpty"},{x,0}}.
{call_ext,1,{extfunc,print,new,1}}.
{move,{x,0},{y,0}}.
{move,{literal,"Dumpty"},{x,0}}.
{call_ext,1,{extfunc,print,new,1}}.
{move,{x,0},{x,3}}.
{move,{y,0},{x,1}}.
{move,{atom,message},{x,2}}.
{move,{literal,"Hello!"},{x,0}}.
{move,{x,3},{y,0}}.
{apply,1}.
{move,{y,0},{x,1}}.
{move,{atom,message},{x,2}}.
{trim,1,0}.
{move,{literal,"Hi!"},{x,0}}.
{apply,1}.
{move,{atom,ok},{x,0}}.
{deallocate,0}.
return.
{function, module_info, 0, 4}.
{label,3}.
{func_info,{atom,main},{atom,module_info},0}.
{label,4}.
{move,{atom,main},{x,0}}.
{call_ext_only,1,{extfunc,erlang,get_module_info,1}}.
{function, module_info, 1, 6}.
{label,5}.
{func_info,{atom,main},{atom,module_info},1}.
{label,6}.
{move,{x,0},{x,1}}.
{move,{atom,main},{x,0}}.
{call_ext_only,2,{extfunc,erlang,get_module_info,2}}.
root@nd-desktop:~/test/m# cat print.S
{module, print}. %% version = 0
{exports, [{instance,1},{message,2},{module_info,0},{module_info,1},{new,1}]}.
{attributes, [{abstract,[true]}]}.
{labels, 11}.
{function, new, 1, 2}.
{label,1}.
{func_info,{atom,print},{atom,new},1}.
{label,2}.
{call_only,1,{f,4}}.
{function, instance, 1, 4}.
{label,3}.
{func_info,{atom,print},{atom,instance},1}.
{label,4}.
{test_heap,3,1}.
{put_tuple,2,{x,1}}.
{put,{atom,print}}.
{put,{x,0}}.
{move,{x,1},{x,0}}.
return.
{function, message, 2, 6}.
{label,5}.
{func_info,{atom,print},{atom,message},2}.
{label,6}.
{test,is_tuple,{f,5},[{x,1}]}.
{test,test_arity,{f,5},[{x,1},2]}.
{allocate_heap,0,4,2}.
{get_tuple_element,{x,1},1,{x,2}}.
{put_list,{x,0},nil,{x,0}}.
{put_list,{x,2},{x,0},{x,1}}.
{move,{literal,"~s: '~s'~n"},{x,0}}.
{call_ext,2,{extfunc,io,fwrite,2}}.
{move,{atom,ok},{x,0}}.
{deallocate,0}.
return.
{function, module_info, 0, 8}.
{label,7}.
{func_info,{atom,print},{atom,module_info},0}.
{label,8}.
{move,{atom,print},{x,0}}.
{call_ext_only,1,{extfunc,erlang,get_module_info,1}}.
{function, module_info, 1, 10}.
{label,9}.
{func_info,{atom,print},{atom,module_info},1}.
{label,10}.
{move,{x,0},{x,1}}.
{move,{atom,print},{x,0}}.
{call_ext_only,2,{extfunc,erlang,get_module_info,2}}.
先看
print.S
这个模块导出的函数除了module_info外还有3个导出 new/1, instance/1, 更重要的是 message在源码里面是/1, 但是导出是/2,很奇怪是吗?
我们再看下 new 和 instance 返回一个tuple{print, Arg}.
message函数呢?
{test,is_tuple,{f,5},[{x,1}]}. %% 判断第二个参数是否是tuple
{test,test_arity,{f,5},[{x,1},2]}. %%tuple的个数是不是2
{allocate_heap,0,4,2}.
{get_tuple_element,{x,1},1,{x,2}}. %%取出tuple的第二个参数, 这个就是源码里面的Name模块参数
{put_list,{x,0},nil,{x,0}}.
{put_list,{x,2},{x,0},{x,1}}.
我演示下:
root@nd-desktop:~/test/m# erl
Erlang R13B02 (erts-5.7.3) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.3 (abort with ^G)
1> m(print).
Module print compiled: Date: September 25 2009, Time: 07.23
Compiler options: [{cwd,"/root/test/m"},{outdir,"/root/test/m"}]
Object file: /root/test/m/print.beam
Exports:
instance/1
message/2
module_info/0
module_info/1
new/1
ok
2> print:new(tag1).
{print,tag1}
3> print:instance(tag2).
{print,tag2}
4> print:message("hello", print:new(tag1)).
tag1: 'hello'
ok
main.S 就是这么调用我们的print模块的。
{move,{literal,"Dumpty"},{x,0}}.
{call_ext,1,{extfunc,print,new,1}}.
{move,{x,0},{x,3}}.
{move,{y,0},{x,1}}.
{move,{atom,message},{x,2}}.
{move,{literal,"Hello!"},{x,0}}.
{move,{x,3},{y,0}}.
{apply,1}.
压入的参数正是 (Message, print:new(tag)), 然后是apply opcode.
我们看下apply opcode:
OpCase(i_apply): {
Eterm* next;
SWAPOUT;
next = apply(c_p, r(0), x(1), x(2), reg);
SWAPIN;
if (next != NULL) {
r(0) = reg[0];
SET_CP(c_p, I+1);
SET_I(next);
Dispatch();
}
I = handle_error(c_p, I, reg, apply_3);
goto post_error_handling;
}
static Uint*
apply(Process* p, Eterm module, Eterm function, Eterm args, Eterm* reg)
{
int arity;
Export* ep;
Eterm tmp, this;
/*
* Check the arguments which should be of the form apply(Module,
* Function, Arguments) where Function is an atom and
* Arguments is an arity long list of terms.
*/
if (is_not_atom(function)) {
/*
* No need to test args here -- done below.
*/
error:
p->freason = BADARG;
error2:
reg[0] = module;
reg[1] = function;
reg[2] = args;
return 0;
}
/* The module argument may be either an atom or an abstract module
* (currently implemented using tuples, but this might change).
*/
this = THE_NON_VALUE;
if (is_not_atom(module)) {
Eterm* tp;
if (is_not_tuple(module)) goto error;
tp = tuple_val(module);
if (arityval(tp[0]) < 1) goto error;
this = module;
module = tp[1];
if (is_not_atom(module)) goto error;
}
/*
* Walk down the 3rd parameter of apply (the argument list) and copy
* the parameters to the x registers (reg[]). If the module argument
* was an abstract module, add 1 to the function arity and put the
* module argument in the n+1st x register as a THIS reference.
*/
tmp = args;
arity = 0;
while (is_list(tmp)) {
if (arity < (MAX_REG - 1)) {
reg[arity++] = CAR(list_val(tmp));
tmp = CDR(list_val(tmp));
} else {
p->freason = SYSTEM_LIMIT;
goto error2;
}
}
if (is_not_nil(tmp)) { /* Must be well-formed list */
goto error;
}
if (this != THE_NON_VALUE) {
reg[arity++] = this;
}
...
}
上面的代码就是干我们上面解释的事情的。
结论: 模块参数化完全是个语法糖, 没有任何magic。所以这个特性在hipe下也可以放心大胆用。