作者
Björn Gustavsson <bjorn(at)erlang(dot)org>
状态
已接受/19.0 已在 OTP 19 版本中实现
类型
标准跟踪
创建时间
2015年10月27日
Erlang 版本
OTP-19.0
发布历史
2015年10月29日,2015年11月9日,2015年11月11日,2015年11月16日

EEP 45:用于函数名和元数的新宏 #

摘要 #

本 EEP 提议引入两个新的宏,名为 FUNCTION_NAMEFUNCTION_ARITY,它们将分别返回当前函数的名称和元数。

规范 #

新的预定义宏 FUNCTION_NAME 扩展为当前函数的名称(以原子形式)。新的预定义宏 FUNCTION_ARITY 扩展为当前函数的元数(以整数形式)。示例

a_function(_, _) ->
  {?FUNCTION_NAME,?FUNCTION_ARITY}.

预处理后,该示例将如下所示

a_function(_, _) ->
  {a_function,2}.

预处理器将在扩展 FUNCTION_NAMEFUNCTION_ARITY 宏之前扩展所有其他宏。因此,如果我们有以下示例

-define(F, a_function).
?F(_, _) ->
  {?FUNCTION_NAME,?FUNCTION_ARITY}.

预处理器将首先将其扩展为

a_function(_, _) ->
  {?FUNCTION_NAME,?FUNCTION_ARITY}.

然后扩展为

a_function(_, _) ->
  {a_function,2}.

FUNCTION_NAMEFUNCTION_ARITY 宏可以以任何以原子开头,后跟左括号的形式使用(当所有其他宏都已扩展时)。这些宏甚至可以在函数头中使用。因此,以下示例是合法的(尽管不是很有用)

a(?FUNCTION_NAME) -> ok.

它将被扩展为

a(a) -> ok.

FUNCTION_NAMEFUNCTION_ARITY 宏在函数头中存在其他宏的情况下也可以工作。示例

-define(__, _, _).
b(?FUNCTION_NAME, ?FUNCTION_ARITY, ?__) ->
   ok.

这段代码将首先被扩展为

b(?FUNCTION_NAME, ?FUNCTION_ARITY, _, _) ->
   ok.

然后扩展为

b(b, 4, _, _) ->
  ok.

在属性中使用 FUNCTION_NAMEFUNCTION_ARITY 将导致编译错误。示例

-attr(?FUNCTION_NAME).

错误消息将如下所示

example.erl:4: ?FUNCTION_NAME can only be used within a function

FUNCTION_NAMEFUNCTION_ARITY 的调用不能开始一个形式。因此,以下示例是非法的

?FUNCTION_NAME() -> ok.

错误消息将如下所示

example.erl:4: ?FUNCTION_NAME must not begin a form

实现要求 #

本 EEP 没有明确指定应如何实现 FUNCTION_NAMEFUNCTION_ARITY 宏,但它确实对实现提出了一些要求

  • 实现必须是高效的。特别是,不使用 FUNCTION_NAMEFUNCTION_ARITY 宏的模块不应有任何明显的减速。

  • FUNCTION_NAMEFUNCTION_ARITY 的扩展必须由 epp 模块完成。将宏的扩展推迟到以后的编译器传递是不可接受的,因为它可能导致与在抽象格式上操作的解析转换和其他工具的兼容性问题。

示例 #

-define(FUNCTION_STRING, atom_to_list(?FUNCTION_NAME) ++ "/" ++
          integer_to_list(?FUNCTION_ARITY)).

test() ->
  ?FUNCTION_STRING.

test/0 函数将返回 "test/0"。请注意,BEAM 编译器将在编译时评估常量表达式;因此,FUNCTION_STRING 将在编译期间转换为字符串字面量。

c() ->
  F = fun() -> ?FUNCTION_NAME end,
  F().

c/0 函数将返回 c

在创建引用包含函数的 fun 时可以使用宏

self_ref(Data, Handler) ->
    ...
    Handler(Data, fun ?FUNCTION_NAME/?FUNCTION_ARITY)
    ...

动机 #

许多用户都要求提供某种可以返回当前函数名称的宏,类似于 FILELINEMODULE。例如:为什么没有 ?FUNCTION 宏

函数名称宏最常见的用例似乎是将信息记录到日志文件中。可能的解决方法包括使用解析转换、使用 process_info/2 或生成并捕获异常。除非应用程序出于其他原因需要解析转换,否则仅为了捕获当前函数的名称而实现解析转换是麻烦的。其他解决方法具有运行时开销。

原理 #

为什么不仅仅是一个 FUNCTION 宏?#

为了最大程度地减少预处理器符号命名空间的污染,是否不应该只有一个 FUNCTION 宏,该宏将返回一个包含当前函数的名称和元数的元组?

当然可以,但是许多常见的用例会有些麻烦

io:format("~p/~p: ~s\n", [element(1, ?FUNCTION),
                          element(2, ?FUNCTION),
                          Message])

将其与更具可读性的

io:format("~p/~p: ~s\n", [?FUNCTION_NAME,
                          ?FUNCTION_ARITY,
                          Message])

还有一些情况下,element(1, ?FUNCTION)element(2, ?FUNCTION) 是非法的,例如在函数头中或 fun 关键字之后。以下示例将无法编译

fun element(1, ?FUNCTION)/element(2, ?FUNCTION)

为什么我必须自己定义 FUNCTION_STRING#

最重要的原因是存在两个合理的定义

-define(FUNCTION_STRING,
   atom_to_list(?FUNCTION_NAME) ++ "/" ++
   integer_to_list(?FUNCTION_ARITY)).

-define(FUNCTION_STRING,
   ?MODULE_STRING ++ ":" ++
   atom_to_list(?FUNCTION_NAME) ++ "/" ++
   integer_to_list(?FUNCTION_ARITY)).

自己定义 FUNCTION_STRING 没有运行时开销,因为编译器会将 FUNCTION_STRING 的任何定义在编译期间转换为文字字符串。

另一个原因是避免用多余的预定义宏来污染宏命名空间。

历史说明:MODULE_STRING 作为 OTP R7B 中的优化添加,因为当时编译器没有像现在这样优化常量表达式。

为什么允许在函数头中使用 FUNCTION_NAMEFUNCTION_ARITY#

我看不到在函数头中使用 FUNCTION_NAMEFUNCTION_ARITY 宏的任何实际用途。仅允许在函数体中使用它们才有意义。但是请考虑以下示例

f(a, _) ->
  ok;
f(?FUNCTION_NAME, ?FUNCTION_ARITY) ->
  ok.

为了能够拒绝在第一个子句之外的子句中调用 FUNCTION_NAMEFUNCTION_ARITY,预处理器必须基本上能够解析任意 Erlang 代码。唯一的实际解决方案是使用 erl_parse 模块中现有的解析器。这将减慢预处理器的速度,而不会提供任何额外的好处。

向后兼容性 #

定义 FUNCTION_NAMEFUNCTION_ARITY 的模块将无法编译,并显示类似于以下消息

example.erl:4: redefining predefined macro 'FUNCTION_NAME'

同样,尝试使用 -D 从命令行定义 FUNCTION_NAMEFUNCTION_ARITY 也会失败。

实现 #

对于不使用 FUNCTION_NAMEFUNCTION_ARITY 宏的函数,参考实现的额外开销基本为零。仅当看到调用 FUNCTION_NAMEFUNCTION_ARITY 时,预处理器才会开始扫描以确定当前函数的名称和元数。扫描将在找到参数列表末尾的右括号后立即停止。如果同一函数定义中看到另一个对 FUNCTION_NAMEFUNCTION_ARITY 的调用,则将保存并重用名称和元数。

可以从 Github 上获取参考实现,如下所示

git fetch git://github.com/bjorng/otp.git bjorn/function-macro

版权 #

本文档已置于公共领域。