Erlang的宏

Erlang的宏的定义语法如下:

1
2
-define(Const, Replacement).
-define(Func(Var1,...,VarN), Replacement).

宏定义可以放在模块的属性和函数声明之间的任何地方,但是,宏使用前必须被定义。如果一个宏被几个模块使用,则建议将该宏的定义放置在一个包含文件里。

宏的使用语法如下:

1
2
?Const
?Func(Arg1,...,ArgN)

宏在编译期间被展开替换,如下代码:

1
2
3
4
-define(TIMEOUT, 200).
...
call(Request) ->
server:call(refserver, Request, ?TIMEOUT).

编译期间被替换为:

1
2
call(Request) ->
server:call(refserver, Request, 200).

有参数的宏在编译期间,其参数被实参替换,代码如下所示:

1
2
3
4
5
-define(MACRO1(X, Y), {a, X, b, Y}).
...
bar(X) ->
?MACRO1(a, b),
?MACRO1(X, 123)

编译期间被替换为:

1
2
3
bar(X) ->
{a,a,b,b},
{a,X,b,123}.

如果想看宏展开的效果,可以在编译模块的时候使用 ‘P’ 选项:

1
compile:file(File, \['P'\]).

这将产生宏展开后的文件:File.P 。

Erlang预定义了一些宏:

1
2
3
4
5
6
7
8
9
?MODULE %%当前模块的名字
?MODULE_STRING %%当前模块的名字的字符串
?FILE %%当前模块的文件名
?LINE %%当前行号
?MACHINE %%虚拟机的名字,固定为'BEAM'

除了预定义宏之外,宏是可以重载的。重载宏有多个定义,每个定义具有不同数量的参数。参数化宏在使用的时候,即使没有型参,也要加上括号。如果有型参数,则实参个数必须与型参一致。

宏可以应用于条件编译。如下指令:

1
2
3
4
5
6
7
8
9
-undef(Macro). %%取消宏Macro的定义。
-ifdef(Macro). %%如果宏Macro定义了,则编译其下面的语句。
-ifndef(Macro). %%如果宏Macro没有定义了,则编译其下面的语句。
-else. %%这个指令只允许出现在 ifdef 或 ifndef 的后面。仅当它前面的ifdef 或 ifndef 为false时,它下面的语句才被编译。
-endif. %%指示指令 ifdef 或 ifndef 的结束。

注意:上述指令不能出现在函数当中。

例子如下:

1
2
3
4
5
6
7
8
9
10
-module(m).
...
-ifdef(debug).
-define(LOG(X), io:format("{~p,~p}: ~p~n", \[?MODULE,?LINE,X\])).
-else.
-define(LOG(X), true).
-endif.
...

如果想要LOG(X)宏真正输出内容,则在编译的时候要加上 debug 宏:

1
% erlc -Ddebug m.erl


1
2
1> c(m, {d, debug}).
{ok,m}

这样?LOG(Arg)就会展开为对 io:format/2 的调用。

如果Arg是宏的参数,那么??Arg将展开为包含这个参数的字符串。这个和C语言中宏参数字符串化类似:#arg

例如:

1
2
3
4
-define(TESTCALL(Call), io:format("Call ~s: ~w~n", \[??Call, Call\])).
?TESTCALL(myfunction(1,2)),
?TESTCALL(you:function(2,1)).

它的结果是:

1
2
io:format("Call ~s: ~w~n",\["myfunction ( 1 , 2 )",myfunction(1,2)\]),
io:format("Call ~s: ~w~n",\["you : function ( 2 , 1 )",you:function(2,1)\]).