我目前正在关注Fred Herbert所著的《Learn You Some Erlang for Great Good》一书,其中一节是关于宏的。
我理解将宏用于变量(主要是常量值),但是,我不理解宏作为函数的用例。例如,赫伯特写道:
定义一个“函数”宏是类似的。这是一个简单的宏,用于从另一个数字中减去一个数字:
-define(sub(X, Y), X-Y).
Run Code Online (Sandbox Code Playgroud)
为什么不将其定义为其他地方的函数?为什么要使用宏?编译器是否有某种性能优势,或者这仅仅是“这个函数如此简单,让我们在一行中定义它”类型的事情?
我不是要开始辩论或偏好争论,但在看到一些生产 Erlang 代码后,我开始注意到大量宏函数的使用。
在这种情况下,宏不是函数(-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).
Run Code Online (Sandbox Code Playgroud)
该宏可以用作:
my_test() ->
Pid = start_whatever(),
%% ...
?wait_for({'EXIT', Pid, Reason}, 5000),
?assertMatch(shutdown, Reason).
Run Code Online (Sandbox Code Playgroud)
通过这样做,我能够在某些测试中简化文本形式,而无需大量嵌套,并且以函数无法实现的方式。
请注意,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)()).
Run Code Online (Sandbox Code Playgroud)
这同样可以让您携带图案和装订,并制作出其他方式不可能实现的奇特形式。
在最后一个例子中,您会注意到我使用了?LINE宏。这是宏的另一个优点:您可以保留有关调用站点的信息和位置,例如其模块名称、行号等。当需要此类元数据时(例如当您报告测试失败时),这非常有用。