我目前正在关注弗雷德赫伯特的书“了解你的好东西
。我理解使用宏作为变量(主要是常量值),但是,我不理解宏作为函数的用例。例如,赫伯特写道:
定义"功能"宏是类似的。这是一个简单的宏,用于从另一个数字中减去一个数字:
-define(sub(X, Y), X-Y).
为什么不将其定义为其他地方的功能呢?为什么要使用宏?编译器是否具有某种性能优势,或者这只是一个" 这个功能非常简单,让我们在一行中定义它"事物的类型?
我没有尝试开始辩论或偏好论证,但在看到一些生产Erlang代码之后,我开始注意到大量的宏功能使用。
答案 0 :(得分:3)
在这种情况下,宏不是函数(-define(sub(X, Y), X-Y)
,它会更安全-define(sub(X, Y), (X-Y))
)的一个显而易见的优点是,它可以用作保护,因为自定义函数调用是禁止的。
在许多情况下,将函数定义为内联函数会更安全。
另一方面,还有其他一些有趣的案例,例如测试中的断言或快捷方式,你想要的是在最后的位置保留一些本地环境。
例如,假设我想对一个测试进行泛型调用,其中目标是“匹配给定模式并返回给定值,或者在M毫秒后失败”。
由于模式不是允许随身携带的数据结构,因此无法使用代码实现这种通用性。但是,使用宏:
-define(wait_for(PAT, Timeout),
receive
PAT -> VAL
after Timeout ->
error(timeout)
end).
然后可以将此宏用作:
my_test() ->
Pid = start_whatever(),
%% ...
?wait_for({'EXIT', Pid, Reason}, 5000),
?assertMatch(shutdown, Reason).
通过这样做,我可以在一些测试中简化文本的形式,而不需要一堆嵌套,并且以一种功能无法实现的方式。
请注意,eunit定义的断言本身使用的是函数宏,并且做了类似于
的事情。-define(assertMatch(PAT, TERM),
%% funs to avoid leaking bindings into parent scope
(fun() ->
try
PAT = TERM,
true
catch _:_ ->
error({assertion_failed, ?LINE, ...})
end
end)()).
这同样可以让你携带图案和装订,并做一些其他方式无法实现的花哨形式。
在最后一种情况下,您会注意到我使用了?LINE
宏。这是宏的另一个优点:您保留有关呼叫站点的信息和位置,例如其模块名称,行号等。当需要此类元数据时(例如,当您报告测试失败时),这非常有用。
答案 1 :(得分:2)
如果您正在查看旧代码,可能会使用宏作为内联小函数的方法,假设函数调用非常昂贵。我不确定这是不是真的,但今天你不需要担心。
宏可用于定义常量,例如
-define(MAX_TIMEOUT, 30 * 1000).
%% ...
gen_server:call(my_server, {do_stuff, Data}, ?MAX_TIMEOUT),
%% ...
我最喜欢为这份工作传递环境变量,但是在启动时读取它们并将它们存放在某处并写入访问器是更多的工作。
最后,你可以做一些简单的元编程:
-define(MAKE_REQUEST_FUN(Method),
Method(Request, HTTPOptions, Options) ->
httpc:request(Method, Request, HTTPOptions, Options)).
?MAKE_REQUEST_FUN(get).
?MAKE_REQUEST_FUN(put).
%% Now we've defined a get/3 that can be called as
%% get(Request, [], []).