erlang的模块参数化及其实现

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下也可以放心大胆用。

你可能感兴趣的:(C++,c,erlang,F#,ext)