6.函数 | 6. Functions
6 函数
6.1模式匹配
编译器对函数头以及in case
和receive
子句中的模式匹配进行了优化。除少数例外情况外,重新安排条款没有任何好处。
二进制文件的模式匹配是一个例外。编译器不会重新排列与二进制文件匹配的子句。放置与最后一个
空的二进制文件匹配的子句通常比放置第一个
要快一些。
以下是一个相当不自然的例子来显示另一个例外:
不要
atom_map1(one) -> 1;
atom_map1(two) -> 2;
atom_map1(three) -> 3;
atom_map1(Int) when is_integer(Int) -> Int;
atom_map1(four) -> 4;
atom_map1(five) -> 5;
atom_map1(six) -> 6.
问题是变量的子句Int
。由于变量可以匹配任何东西,包括原子four
,five
以及six
以下子句也匹配,编译器必须生成次优代码,其执行如下:
- 首先,输入值与
one
,two
,和three
(使用单个指令,做了二分搜索,因此,即使有许多值非常有效),以选择前三个子句之一执行哪个(如果有的话)。
- 如果没有前三个子句匹配,则第四个子句匹配作为变量始终匹配。
- 如果保护测试
is_integer(Int)
成功,则执行第四个条款。
- 如果保护测试失败,以进行比较的输入值
four
,five
和six
,并且选择适当的子句。(function_clause
如果没有任何值匹配,则会出现异常。)
重写为:
做
atom_map2(one) -> 1;
atom_map2(two) -> 2;
atom_map2(three) -> 3;
atom_map2(four) -> 4;
atom_map2(five) -> 5;
atom_map2(six) -> 6;
atom_map2(Int) when is_integer(Int) -> Int.
或:
做
atom_map3(Int) when is_integer(Int) -> Int;
atom_map3(one) -> 1;
atom_map3(two) -> 2;
atom_map3(three) -> 3;
atom_map3(four) -> 4;
atom_map3(five) -> 5;
atom_map3(six) -> 6.
提供稍微更有效的匹配代码。
另一个例子:
不要
map_pairs1(_Map, [], Ys) ->
Ys;
map_pairs1(_Map, Xs, [] ) ->
Xs;
map_pairs1(Map, [X|Xs], [Y|Ys]) ->
[Map(X, Y)|map_pairs1(Map, Xs, Ys)].
第一个参数不是
问题。它是可变的,但它是所有子句中的变量。问题是第二个参数中的变量Xs
,在中间子句中。由于变量可以匹配任何内容,因此编译器不允许重新排列子句,但必须生成与写入顺序相匹配的代码。
如果函数被重写如下,编译器可以自由地重新排列子句:
做
map_pairs2(_Map, [], Ys) ->
Ys;
map_pairs2(_Map, [_|_]=Xs, [] ) ->
Xs;
map_pairs2(Map, [X|Xs], [Y|Ys]) ->
[Map(X, Y)|map_pairs2(Map, Xs, Ys)].
编译器将生成类似如下的代码:
不要(已经由编译器完成)
explicit_map_pairs(Map, Xs0, Ys0) ->
case Xs0 of
[X|Xs] ->
case Ys0 of
[Y|Ys] ->
[Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
[] ->
Xs0
end;
[] ->
Ys0
end.
这可能是最常见的情况,输入列表不是空的或非常短的。(另一个优点是透析器可以为Xs
变量推导出更好的类型。)
6.2函数调用
这是针对不同呼叫的相对成本的故意粗略指南。它基于在Solaris / Sparc上运行的基准测试数据:
- 本地调用或外部函数(
foo()
,m:foo()
)是最快的调用。
- 调用或应用趣味(
Fun()
,apply(Fun, [])
)大约是调用本地函数的三倍
。
- 应用导出的函数(
Mod:Name()
,apply(Mod, Name, [])
)大约是调用fun的两倍,或者大约是调用本地函数的六倍
。
注释和实现细节
调用和应用趣味不涉及任何散列表查找。fun包含一个指向实现fun的函数的(间接)指针。
apply/3
必须查找要在哈希表中执行的函数的代码。因此,它总是比直接电话或有趣的电话慢。
(从性能角度来看)你是否写下了以下内容:
Module:Function(Arg1, Arg2)
或:
apply(Module, Function, [Arg1,Arg2])
编译器在内部将后一段代码重写为前者。
下面的代码稍微慢一些,因为参数列表的形状在编译时是未知的。
apply(Module, Function, Arguments)
6.3 递归中的内存使用
在编写递归函数时,最好使它们成为尾递归的,以便它们可以在常量内存空间中执行:
做
list_length(List) ->
list_length(List, 0).
list_length([], AccLen) ->
AccLen; % Base case
list_length([_|Tail], AccLen) ->
list_length(Tail, AccLen + 1). % Tail-recursive
不要
list_length([]) ->
0. % Base case
list_length([_ | Tail]) ->
list_length(Tail) + 1. % Not tail-recursive