本 EEP 提议引入两个新的宏,名为 FUNCTION_NAME
和 FUNCTION_ARITY
,它们将分别返回当前函数的名称和元数。
新的预定义宏 FUNCTION_NAME
扩展为当前函数的名称(以原子形式)。新的预定义宏 FUNCTION_ARITY
扩展为当前函数的元数(以整数形式)。示例
a_function(_, _) ->
{?FUNCTION_NAME,?FUNCTION_ARITY}.
预处理后,该示例将如下所示
a_function(_, _) ->
{a_function,2}.
预处理器将在扩展 FUNCTION_NAME
和 FUNCTION_ARITY
宏之前扩展所有其他宏。因此,如果我们有以下示例
-define(F, a_function).
?F(_, _) ->
{?FUNCTION_NAME,?FUNCTION_ARITY}.
预处理器将首先将其扩展为
a_function(_, _) ->
{?FUNCTION_NAME,?FUNCTION_ARITY}.
然后扩展为
a_function(_, _) ->
{a_function,2}.
FUNCTION_NAME
和 FUNCTION_ARITY
宏可以以任何以原子开头,后跟左括号的形式使用(当所有其他宏都已扩展时)。这些宏甚至可以在函数头中使用。因此,以下示例是合法的(尽管不是很有用)
a(?FUNCTION_NAME) -> ok.
它将被扩展为
a(a) -> ok.
FUNCTION_NAME
和 FUNCTION_ARITY
宏在函数头中存在其他宏的情况下也可以工作。示例
-define(__, _, _).
b(?FUNCTION_NAME, ?FUNCTION_ARITY, ?__) ->
ok.
这段代码将首先被扩展为
b(?FUNCTION_NAME, ?FUNCTION_ARITY, _, _) ->
ok.
然后扩展为
b(b, 4, _, _) ->
ok.
在属性中使用 FUNCTION_NAME
或 FUNCTION_ARITY
将导致编译错误。示例
-attr(?FUNCTION_NAME).
错误消息将如下所示
example.erl:4: ?FUNCTION_NAME can only be used within a function
FUNCTION_NAME
或 FUNCTION_ARITY
的调用不能开始一个形式。因此,以下示例是非法的
?FUNCTION_NAME() -> ok.
错误消息将如下所示
example.erl:4: ?FUNCTION_NAME must not begin a form
本 EEP 没有明确指定应如何实现 FUNCTION_NAME
和 FUNCTION_ARITY
宏,但它确实对实现提出了一些要求
实现必须是高效的。特别是,不使用 FUNCTION_NAME
或 FUNCTION_ARITY
宏的模块不应有任何明显的减速。
FUNCTION_NAME
和 FUNCTION_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)
...
许多用户都要求提供某种可以返回当前函数名称的宏,类似于 FILE
、LINE
和 MODULE
。例如:为什么没有 ?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_NAME
和 FUNCTION_ARITY
? #我看不到在函数头中使用 FUNCTION_NAME
和 FUNCTION_ARITY
宏的任何实际用途。仅允许在函数体中使用它们才有意义。但是请考虑以下示例
f(a, _) ->
ok;
f(?FUNCTION_NAME, ?FUNCTION_ARITY) ->
ok.
为了能够拒绝在第一个子句之外的子句中调用 FUNCTION_NAME
和 FUNCTION_ARITY
,预处理器必须基本上能够解析任意 Erlang 代码。唯一的实际解决方案是使用 erl_parse
模块中现有的解析器。这将减慢预处理器的速度,而不会提供任何额外的好处。
定义 FUNCTION_NAME
或 FUNCTION_ARITY
的模块将无法编译,并显示类似于以下消息
example.erl:4: redefining predefined macro 'FUNCTION_NAME'
同样,尝试使用 -D
从命令行定义 FUNCTION_NAME
或 FUNCTION_ARITY
也会失败。
对于不使用 FUNCTION_NAME
或 FUNCTION_ARITY
宏的函数,参考实现的额外开销基本为零。仅当看到调用 FUNCTION_NAME
或 FUNCTION_ARITY
时,预处理器才会开始扫描以确定当前函数的名称和元数。扫描将在找到参数列表末尾的右括号后立即停止。如果同一函数定义中看到另一个对 FUNCTION_NAME
或 FUNCTION_ARITY
的调用,则将保存并重用名称和元数。
可以从 Github 上获取参考实现,如下所示
git fetch git://github.com/bjorng/otp.git bjorn/function-macro
本文档已置于公共领域。