什么时候在 Erlang 中使用宏函数?

car*_*ost 5 macros erlang

我目前正在关注Fred Herbert所著的《Learn You Some Erlang for Great Good》一书,其中一节是关于宏的。

我理解将宏用于变量(主要是常量值),但是,我不理解宏作为函数的用例。例如,赫伯特写道:

定义一个“函数”宏是类似的。这是一个简单的宏,用于从另一个数字中减去一个数字:

-define(sub(X, Y), X-Y).
Run Code Online (Sandbox Code Playgroud)

为什么不将其定义为其他地方的函数?为什么要使用宏?编译器是否有某种性能优势,或者这仅仅是“这个函数如此简单,让我们在一行中定义它”类型的事情?

我不是要开始辩论或偏好争论,但在看到一些生产 Erlang 代码后,我开始注意到大量宏函数的使用。

I G*_*ICE 4

在这种情况下,宏不是函数(-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宏。这是宏的另一个优点:您可以保留有关调用站点的信息和位置,例如其模块名称、行号等。当需要此类元数据时(例如当您报告测试失败时),这非常有用。