Erlang中的list函数如何失败?

很久以前写过一篇The Asymmetry of ++的文章,感谢Fede Bergero的发现。 让我们在不对称列表中添加更多…

注意:本文基于 OTP23。 该版本之后,许多事情都得到了改进。

介绍

最初,我想把这篇文章写成一个故事(就像我通常做的那样),但我突然掌握了太多的信息。 所以,我选择从table、cheatsheet开始。

该列表的目的是显示lists模块中的不同高阶函数在输入错误时如何反应,特别是在它们期望函数的参数中。 这个错误的输入可能是……

  • 不是函数的东西(例如 lists:map(not_fun, [1,2,3]);
  • 具有错误的参数数量的函数(例如 lists:map(fun() -> wat end, [a])

空列表

在我们开始之前,现在这里揭示一半的问题。让我向您介绍第一个不对称:

1> lists:map(something_wrong, [1,2,3]). % A non-empty list
** exception error: bad function something_wrong
     in function  lists:map/2 (lists.erl, line 1243)
2> lists:map(something_wrong, []). % An empty list
** exception error: no function clause matching lists:map(something_wrong,[]) (lists.erl, line 1242)

这发生在您将在下面看到的所有函数中:如果您使用错误的第一个参数和一个空列表调用这些函数,您将收到 function_clause 错误。 如果你用根本不是列表的东西来调用它们,也会发生同样的情况。 如果您使用非空列表调用它们,那么……继续阅读;)

至少,当 list 参数为空列表时,lists 模块会持续引发 function_clause 错误。 这是一件好事。

非空列表

当列表不为空时会发生什么? 为了弄清楚这一点,我使用下面这个代码进行试验:

-module(a_module).

-format #{inline_clause_bodies => true,
inline_items => all,
paper => 100}.

-export([run/0]).

run() ->
Arity2 =
[all, any, dropwhile, filter, filtermap, flatmap, foreach, map, partition, search, sort,
splitwith, takewhile, usort],
Arity3 = [foldl, foldr, keymap, mapfoldl, mapfoldr],
Result =
[[fun_name(F, 2) | run2(fun lists:F/2)] || F <- Arity2]
++ [[fun_name(F, 3) | run3(fun lists:F/3)] || F <- Arity3],
format(Result),
Result.

fun_name(Name, Arity) -> io_lib:format("~p/~p", [Name, Arity]).

run2(Function) ->
[test(fun() -> Function(not_a_function, sample_list()) end),
test(fun() -> Function(fun wrong_arity/0, sample_list()) end)].

run3(Function) ->
[test(fun() -> Function(not_a_function, 1, sample_list()) end),
test(fun() -> Function(fun wrong_arity/0, 1, sample_list()) end)].

test(Fun) ->
try
Fun()
catch
_:{Err, _} -> Err;
_:Err -> Err
end.

wrong_arity() -> "no list function expects a 0-arity fun as a parameter".

sample_list() -> [{this, "is for keymap"}].

format(Result) ->
io:format("Function|No Fun|Bad Fun
"),
io:format("--------|------|-------
"),
lists:foreach(fun(Row) -> io:format("`~s`	| `~p` | `~p`
", Row) end, Result).

这些是我得到的结果……

lists 函数No FunBad Fun
all/2badfunbadarity
any/2badfunbadarity
dropwhile/2badfunbadarity
filter/2function_clausefunction_clause
filtermap/2badfunbadarity
flatmap/2badfunbadarity
foreach/2badfunbadarity
map/2badfunbadarity
partition/2badfunbadarity
search/2badfunbadarity
sort/2function_clausefunction_clause
splitwith/2function_clausefunction_clause
takewhile/2badfunbadarity
usort/2function_clausefunction_clause
foldl/3badfunbadarity
foldr/3function_clausefunction_clause
keymap/3badfunbadarity
mapfoldl/3badfunbadarity
mapfoldr/3function_clausefunction_clause

从中得到的发现

因此,如您所见,如果您根本不使用函数,大多数函数会引发 badfun 错误,如果使用错误的函数 arity 一般会引发 badarity 错误,但其中一些也会引发 function_clause 错误。

关联上下文后,这就是每个错误在shell中的描述……

10> lists:foldl(not_fun, 1, [a,b,c]).
** exception error: bad function not_fun
     in function  lists:foldl/3 (lists.erl, line 1267)
11> lists:foldl(fun() -> {wrong, arity} end, 1, [a,b,c]).
** exception error: interpreted function with arity 0 called with two arguments
     in function  lists:foldl/3 (lists.erl, line 1267)
12> lists:foldr(fun() -> {wrong, arity} end, 1, [a,b,c]).
** exception error: no function clause matching lists:foldr(#Fun<erl_eval.45.79398840>,1,[]) (lists.erl, line 1279)
     in function  lists:foldr/3 (lists.erl, line 1280)

至少从我的角度来看,lists:foldl/3 的错误描述比 list:foldr/3 更清楚。 正如我之前告诉你的,当列表为空时,所有函数看起来都像 lists:foldr/3(即,它们会在每个糟糕的情况下引发 function_clause)。

这是问题吗?

从语义上讲,这不是问题。 到目前为止,所有这些功能都没有针对我所展示的场景进行定义。 因此,我们不应该依赖它们在使用不正确的值调用它们时产生的错误。

但是,如果您是 Erlang 新手或者您正在控制台上尝试一些东西,那么您不太可能错误地调用上述函数之一。 在这种情况下,function_clause 可能会非常令人困惑,而 badfun 或 badarity 可以更轻松地引导您找到问题的解决方案。

此外,如果出于某种原因,您需要编写代码来捕获这些错误并且您不想捕获任何错误(仅是您需要的特定错误),您必须始终小心处理 function_clause 以防您的 输入是一个空列表。

换句话说,这是个问题。

%% @doc similar to lists:all/2 but if the function has the wrong
%%      arity it just returns false.
-spec my_all(fun((...) -> boolean()), [X]) -> boolean().
my_all(Pred, List) ->
    try lists:all(Pred, List)
    catch
        error:{badarity, _} -> false
    end.

因为…

45> a_module:my_all(fun(X) -> true end, [a,b,c]).
true
46> a_module:my_all(fun() -> true end, [a,b,c]).
false
47> a_module:my_all(fun() -> true end, []).
** exception error: no function clause matching lists:all(#Fun<erl_eval.45.79398840>,[]) (lists.erl, line 1216)
     in function  a_module:my_all/2

所以我们不得不像这样来写

%% @doc similar to lists:all/2 but if the function has the wrong
%%      arity it just returns false.
-spec my_all(fun((...) -> boolean()), [X]) -> boolean().
my_all(Pred, List) ->
    try lists:all(Pred, List)
    catch
        error:{badarity, _} -> false
        error:function_clause when List == [] -> false
    end.

我想看到什么?

我认为理想的情况是上表中的所有函数都具有相同的错误行为:

  • 如果他们收到的东西不是他们期望的功能,请提出 badfun,无论列表长度如何。
  • 如果他们收到一个错误的函数,无论列表长度如何,都会提高 badarity。
  • 如果列表或任何其他参数错误(例如,不是列表),请提出 function_clause。

基本上,行为类似于 lists:foldl/3 ,但不要将空列表视为特殊情况。

这是否可行?

是的! 列表模块中的更改实际上并没有那么复杂。 以 lists:map/2 为例,我们“只”需要在此处添加这两个子句:

map(F, [H|T]) ->
    [F(H)|map(F, T)];
map(F, []) when is_function(F, 1) -> [];
map(F, []) when is_function(F) -> error({badarity, {F, []}});
map(_, []) -> error(badfun).

这是否值得?

我不知道……也许吧? 也许这是赌一下的好时机? 🙃

Related Posts

2021 年你需要知道的关于 Erlang 的一切

今天,我们将看一个相当古老且有些古怪的东西。 你们大多数人可能没有注意到的语言。 虽然 Erlang 不像某些现代编程语言那样流行,但它安静地运行着 WhatsApp 和微信等每天为大量用户提供服务的应用程序。 在这篇文章中,我将告诉你关于这门语言的更多事情、它的历史,以及你是否应该考虑自己学习它。 ## 什么是 Erlang,它在哪里使用? Erl

Read More

Erlang JIT中基于类型的优化

这篇文章探讨了 Erlang/OTP 25 中基于类型的新优化,其中编译器将类型信息嵌入到 BEAM 文件中,以帮助JIT(即时编译器)生成更好的代码。 ## 两全其美 OTP 22 中引入的基于SSA的编译器处理步骤进行了复杂的类型分析,允许进行更多优化和更好的生成代码。然而,Erlang 编译器可以做什么样的优化是有限制的,因为 BEAM 文件必须

Read More

Erlang JIT之路

自从Erlang 存在,就一直有让它更快的需求和野心。这篇博文是一堂历史课,概述了主要的 Erlang 实现以及如何尝试提高 Erlang 的性能。 ## Prolog 解释器 Erlang 的第一个版本是在 1986 年在 Prolog 中实现的。那个版本的 Erlang 对于创建真正的应用程序来说太慢了,但它对于找出Erlang语言的哪些功能有用,哪

Read More