【问题标题】:When to use macros functions in Erlang?什么时候在 Erlang 中使用宏函数?
【发布时间】:2016-11-24 04:30:47
【问题描述】:

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

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

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

-define(sub(X, Y), X-Y).

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

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

【问题讨论】:

    标签: macros erlang


    【解决方案1】:

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

    【讨论】:

      【解决方案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, [], []).
      

      【讨论】:

        猜你喜欢
        • 2010-11-09
        • 1970-01-01
        • 2011-07-05
        • 1970-01-01
        • 1970-01-01
        • 2010-12-11
        • 2010-12-28
        • 1970-01-01
        相关资源
        最近更新 更多