如何解释
FunctionClauseError
中的函数属性
%FunctionClauseError{
module: MyApp.MyModule,
function: :"-load_data/4-inlined-0-",
arity: 1,
kind: nil,
args: nil,
clauses: nil
}
术语
-load_data/4-inlined-0-
指向 load_data/4
函数内部的某些内容。inlined-0
?load_data/4
函数体中的第一个宏调用。当编译器内联匿名函数时,会出现
-load_data/4-inlined-0-
函数名,并且对匿名函数的调用失败并出现函数子句错误。它不是实际函数的名称;添加它是为了解决这个问题,标题为“令人困惑的case_clause
异常而不是function_clause
”。我不认为这在任何地方都有记录。
这需要一点历史课。让我们考虑一下这段代码:
-module(foo).
-export([foo/0]).
foo() ->
F = fun(X) when is_atom(X) -> ok end,
F(0).
也就是说,我们创建一个匿名函数,然后我们以导致函数子句错误的方式调用它。
匿名函数的内联是在 Erlang/OTP 24 中引入的。因此,当在 Erlang 23 中运行时,我们可以看到堆栈跟踪中的函数名称是匿名函数的常用名称,带有
-fun-
:
> catch foo:foo().
{'EXIT',{function_clause,[{foo,'-foo/0-fun-0-',
[0],
[{file,"foo.erl"},{line,6}]},
{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,684}]},
{erl_eval,expr,5,[{file,"erl_eval.erl"},{line,437}]},
{shell,exprs,7,[{file,"shell.erl"},{line,686}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]}}
在 Erlang 24 中,该函数由编译器内联,并重写为
case
表达式。因此,我们看到一个 case_clause
异常,并且堆栈跟踪表明错误发生在 foo
函数本身:
> catch foo:foo().
{'EXIT',{{case_clause,{0}},
[{foo,foo,0,[{file,"foo.erl"},{line,6}]},
{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,689}]},
{erl_eval,expr,5,[{file,"erl_eval.erl"},{line,434}]},
{shell,exprs,7,[{file,"shell.erl"},{line,686}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]}}
当然,对于寻找
case
表达式来查找错误原因的程序员来说,这很令人困惑,但没有找到任何原因。这就是为什么 Erlang 25 确保使用实际的 function_clause
异常,以反映错误发生在代码中有函数调用的地方这一事实。它使用 -inlined-
而不是 -fun-
,因为编译后的模块中不再存在匿名函数:
> catch foo:foo().
{'EXIT',{function_clause,[{foo,'-foo/0-inlined-0-',
[0],
[{file,"foo.erl"},{line,6}]},
{erl_eval,do_apply,7,[{file,"erl_eval.erl"},{line,748}]},
{erl_eval,expr,6,[{file,"erl_eval.erl"},{line,480}]},
{shell,exprs,7,[{file,"shell.erl"},{line,691}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,647}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,632}]}]}}