查看源代码 Funs
map
以下函数 double
将列表中的每个元素加倍
double([H|T]) -> [2*H|double(T)];
double([]) -> [].
因此,输入的参数将按如下方式加倍
> double([1,2,3,4]).
[2,4,6,8]
以下函数 add_one
将列表中的每个元素加一
add_one([H|T]) -> [H+1|add_one(T)];
add_one([]) -> [].
函数 double
和 add_one
具有相似的结构。可以通过编写一个函数 map
来表达这种相似性
map(F, [H|T]) -> [F(H)|map(F, T)];
map(F, []) -> [].
现在可以用 map
来表示函数 double
和 add_one
,如下所示
double(L) -> map(fun(X) -> 2*X end, L).
add_one(L) -> map(fun(X) -> 1 + X end, L).
map(F, List)
是一个函数,它接受一个函数 F
和一个列表 L
作为参数,并返回一个新的列表,该列表是通过将 F
应用于 L
中的每个元素而获得的。
从多个不同程序中抽象出共同特征的过程称为过程抽象。过程抽象可用于编写多个结构相似但细节略有不同的函数。具体步骤如下
- 步骤 1. 编写一个函数来表示这些函数的共同特征。
- 步骤 2. 将差异参数化为作为参数传递给公共函数的函数。
foreach
本节演示过程抽象。最初,以下两个示例被编写为传统函数。
此函数将列表的所有元素打印到流中
print_list(Stream, [H|T]) ->
io:format(Stream, "~p~n", [H]),
print_list(Stream, T);
print_list(Stream, []) ->
true.
此函数将消息广播到进程列表
broadcast(Msg, [Pid|Pids]) ->
Pid ! Msg,
broadcast(Msg, Pids);
broadcast(_, []) ->
true.
这两个函数具有相似的结构。它们都迭代列表,并对列表中的每个元素执行某些操作。“某些操作”作为额外的参数传递给执行此操作的函数。
函数 foreach
表示这种相似性
foreach(F, [H|T]) ->
F(H),
foreach(F, T);
foreach(F, []) ->
ok.
使用函数 foreach
,函数 print_list
变为
foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)
使用函数 foreach
,函数 broadcast
变为
foreach(fun(Pid) -> Pid ! M end, L)
foreach
的评估是为了它的副作用而不是它的值。foreach(Fun ,L)
对 L
中的每个元素 X
调用 Fun(X)
,并且处理按照元素在 L
中定义的顺序进行。map
不定义其元素被处理的顺序。
Funs 的语法
Funs 使用以下语法编写(完整描述请参阅Fun 表达式)
F = fun (Arg1, Arg2, ... ArgN) ->
...
end
这将创建一个 N
个参数的匿名函数,并将其绑定到变量 F
。
可以使用以下语法将同一模块中编写的另一个函数 FunctionName
作为参数传递
F = fun FunctionName/Arity
使用这种形式的函数引用,所引用的函数不需要从模块中导出。
也可以使用以下语法引用在不同模块中定义的函数
F = fun Module:FunctionName/Arity
在这种情况下,该函数必须从相关模块中导出。
以下程序说明了创建 fun 的不同方式
-module(fun_test).
-export([t1/0, t2/0]).
-import(lists, [map/2]).
t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
t2() -> map(fun double/1, [1,2,3,4,5]).
double(X) -> X * 2.
可以使用以下语法评估 fun F
F(Arg1, Arg2, ..., Argn)
要检查一个术语是否是 fun,请在 guard 中使用测试 is_function/1
。
示例
f(F, Args) when is_function(F) ->
apply(F, Args);
f(N, _) when is_integer(N) ->
N.
Funs 是一种不同的类型。BIF erlang:fun_info/1,2
可用于检索有关 fun 的信息,而 BIF erlang:fun_to_list/1
返回 fun 的文本表示。check_process_code/2
BIF 如果进程包含依赖于旧版本模块的 fun,则返回 true
。
Fun 内的变量绑定
fun 中出现的变量的作用域规则如下
- fun 的头部中出现的所有变量都被假定为“新的”变量。
- 在 fun 之前定义的变量,以及在 fun 中的函数调用或 guard 测试中出现的变量,具有它们在 fun 之外的值。
- 变量不能从 fun 中导出。
以下示例说明了这些规则
print_list(File, List) ->
{ok, Stream} = file:open(File, write),
foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
file:close(Stream).
这里,在 fun 的头部中定义的变量 X
是一个新变量。在 fun 中使用的变量 Stream
从 file:open
行获取其值。
由于在 fun 的头部中出现的任何变量都被视为新变量,因此以下写法同样有效
print_list(File, List) ->
{ok, Stream} = file:open(File, write),
foreach(fun(File) ->
io:format(Stream,"~p~n",[File])
end, List),
file:close(Stream).
这里,File
被用作新变量,而不是 X
。这不是明智之举,因为 fun 主体中的代码无法引用在 fun 之外定义的变量 File
。编译此示例会给出以下诊断信息
./FileName.erl:Line: Warning: variable 'File'
shadowed in 'fun'
这表明在 fun 内部定义的变量 File
与在 fun 外部定义的变量 File
冲突。
将变量导入 fun 的规则导致某些模式匹配操作必须移入 guard 表达式,而不能写在 fun 的头部。例如,如果希望在参数的值为 Y
时评估 F
的第一个子句,则可以编写以下代码
f(...) ->
Y = ...
map(fun(X) when X == Y ->
;
(_) ->
...
end, ...)
...
而不是编写以下代码
f(...) ->
Y = ...
map(fun(Y) ->
;
(_) ->
...
end, ...)
...
Funs 和模块列表
以下示例显示了与 Erlang shell 的对话。讨论的所有高阶函数都从模块 lists
中导出。
map
lists:map/2
接受一个单参数函数和一个术语列表
map(F, [H|T]) -> [F(H)|map(F, T)];
map(F, []) -> [].
它返回通过将函数应用于列表中的每个参数而获得的列表。
当在 shell 中定义一个新的 fun 时,fun 的值将打印为 Fun#<erl_eval>
> Double = fun(X) -> 2 * X end.
#Fun<erl_eval.6.72228031>
> lists:map(Double, [1,2,3,4,5]).
[2,4,6,8,10]
any
lists:any/2
接受一个单参数谓词 P
和一个术语列表
any(Pred, [H|T]) ->
case Pred(H) of
true -> true;
false -> any(Pred, T)
end;
any(Pred, []) ->
false.
谓词是一个返回 true
或 false
的函数。如果列表中存在一个术语 X
,使得 P(X)
为 true
,则 any
为 true
。
定义一个谓词 Big(X)
,如果其参数大于 10,则该谓词为 true
> Big = fun(X) -> if X > 10 -> true; true -> false end end.
#Fun<erl_eval.6.72228031>
> lists:any(Big, [1,2,3,4]).
false
> lists:any(Big, [1,2,3,12,5]).
true
all
lists:all/2
具有与 any
相同的参数
all(Pred, [H|T]) ->
case Pred(H) of
true -> all(Pred, T);
false -> false
end;
all(Pred, []) ->
true.
如果谓词应用于列表中的所有元素都为 true
,则该谓词为 true
。
> lists:all(Big, [1,2,3,4,12,6]).
false
> lists:all(Big, [12,13,14,15]).
true
foreach
lists:foreach/2
接受一个单参数函数和一个术语列表
foreach(F, [H|T]) ->
F(H),
foreach(F, T);
foreach(F, []) ->
ok.
该函数应用于列表中的每个参数。foreach
返回 ok
。它仅用于其副作用
> lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
1
2
3
4
ok
foldl
lists:foldl/3
接受一个具有两个参数的函数、一个累加器和一个列表
foldl(F, Accu, [Hd|Tail]) ->
foldl(F, F(Hd, Accu), Tail);
foldl(F, Accu, []) -> Accu.
该函数使用两个参数调用。第一个参数是列表中的连续元素。第二个参数是累加器。该函数必须返回一个新的累加器,该累加器在下次调用该函数时使用。
如果您有一个列表的列表 L = ["I","like","Erlang"]
,那么您可以按如下方式求和 L
中所有字符串的长度
> L = ["I","like","Erlang"].
["I","like","Erlang"]
10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
11
lists:foldl/3
的工作方式类似于命令式语言中的 while
循环
L = ["I","like","Erlang"],
Sum = 0,
while( L != []){
Sum += length(head(L)),
L = tail(L)
end
mapfoldl
lists:mapfoldl/3
同时映射和折叠列表
mapfoldl(F, Accu0, [Hd|Tail]) ->
{R,Accu1} = F(Hd, Accu0),
{Rs,Accu2} = mapfoldl(F, Accu1, Tail),
{[R|Rs], Accu2};
mapfoldl(F, Accu, []) -> {[], Accu}.
以下示例演示如何将 L
中的所有字母更改为大写,然后对其进行计数。
首先更改为大写
> Upcase = fun(X) when $a =< X, X =< $z -> X + $A - $a;
(X) -> X
end.
#Fun<erl_eval.6.72228031>
> Upcase_word =
fun(X) ->
lists:map(Upcase, X)
end.
#Fun<erl_eval.6.72228031>
> Upcase_word("Erlang").
"ERLANG"
> lists:map(Upcase_word, L).
["I","LIKE","ERLANG"]
现在,可以同时完成折叠和映射
> lists:mapfoldl(fun(Word, Sum) ->
{Upcase_word(Word), Sum + length(Word)}
end, 0, L).
{["I","LIKE","ERLANG"],11}
filter
lists:filter/2
接受一个单参数谓词和一个列表,并返回列表中所有满足谓词的元素
filter(F, [H|T]) ->
case F(H) of
true -> [H|filter(F, T)];
false -> filter(F, T)
end;
filter(F, []) -> [].
> lists:filter(Big, [500,12,2,45,6,7]).
[500,12,45]
组合映射和过滤器可以编写非常简洁的代码。例如,要定义一个集合差函数 diff(L1, L2)
为列表 L1
和 L2
之间的差,可以按如下方式编写代码
diff(L1, L2) ->
filter(fun(X) -> not member(X, L2) end, L1).
这将给出 L1 中所有不包含在 L2 中的元素的列表。
列表 L1
和 L2
的 AND 交集也很容易定义
intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).
takewhile
lists:takewhile/2
从列表 L
中获取元素 X
,只要谓词 P(X)
为 true
takewhile(Pred, [H|T]) ->
case Pred(H) of
true -> [H|takewhile(Pred, T)];
false -> []
end;
takewhile(Pred, []) ->
[].
> lists:takewhile(Big, [200,500,45,5,3,45,6]).
[200,500,45]
dropwhile
lists:dropwhile/2
是 takewhile
的补集
dropwhile(Pred, [H|T]) ->
case Pred(H) of
true -> dropwhile(Pred, T);
false -> [H|T]
end;
dropwhile(Pred, []) ->
[].
> lists:dropwhile(Big, [200,500,45,5,3,45,6]).
[5,3,45,6]
splitwith
lists:splitwith/2
将列表 L
拆分为两个子列表 {L1, L2}
,其中 L = takewhile(P, L)
且 L2 = dropwhile(P, L)
splitwith(Pred, L) ->
splitwith(Pred, L, []).
splitwith(Pred, [H|T], L) ->
case Pred(H) of
true -> splitwith(Pred, T, [H|L]);
false -> {reverse(L), [H|T]}
end;
splitwith(Pred, [], L) ->
{reverse(L), []}.
> lists:splitwith(Big, [200,500,45,5,3,45,6]).
{[200,500,45],[5,3,45,6]}
返回 Funs 的 Funs
到目前为止,只描述了将 fun 作为参数的函数。还可以编写更强大的、本身返回 fun 的函数。以下示例说明了这些类型的函数。
简单的高阶函数
Adder(X)
是一个函数,给定 X
,它返回一个新的函数 G
,使得 G(K)
返回 K + X
> Adder = fun(X) -> fun(Y) -> X + Y end end.
#Fun<erl_eval.6.72228031>
> Add6 = Adder(6).
#Fun<erl_eval.6.72228031>
> Add6(10).
16
无限列表
这个想法是写一些类似这样的东西
-module(lazy).
-export([ints_from/1]).
ints_from(N) ->
fun() ->
[N|ints_from(N+1)]
end.
然后按如下步骤进行
> XX = lazy:ints_from(1).
#Fun<lazy.0.29874839>
> XX().
[1|#Fun<lazy.0.29874839>]
> hd(XX()).
1
> Y = tl(XX()).
#Fun<lazy.0.29874839>
> hd(Y()).
2
等等。这是一个“惰性嵌入”的例子。
解析
以下示例展示了以下类型的解析器
Parser(Toks) -> {ok, Tree, Toks1} | fail
Toks
是要解析的标记列表。成功的解析返回 {ok, Tree, Toks1}
。
Tree
是一个解析树。Toks1
是Tree
的尾部,其中包含正确解析的结构之后遇到的符号。
不成功的解析返回 fail
。
以下示例说明了一个简单的函数式解析器,该解析器解析语法
(a | b) & (c | d)
以下代码在模块 funparse
中定义了一个函数 pconst(X)
,该函数返回一个解析标记列表的函数
pconst(X) ->
fun (T) ->
case T of
[X|T1] -> {ok, {const, X}, T1};
_ -> fail
end
end.
此函数可以如下使用
> P1 = funparse:pconst(a).
#Fun<funparse.0.22674075>
> P1([a,b,c]).
{ok,{const,a},[b,c]}
> P1([x,y,z]).
fail
接下来,定义了两个高阶函数 pand
和 por
。它们组合原始解析器以生成更复杂的解析器。
首先是 pand
pand(P1, P2) ->
fun (T) ->
case P1(T) of
{ok, R1, T1} ->
case P2(T1) of
{ok, R2, T2} ->
{ok, {'and', R1, R2}};
fail ->
fail
end;
fail ->
fail
end
end.
给定一个语法 G1
的解析器 P1
和一个语法 G2
的解析器 P2
,pand(P1, P2)
返回一个用于语法的解析器,该语法由满足 G1
的标记序列组成,后跟满足 G2
的标记序列。
por(P1, P2)
返回一个用于语法 G1
或 G2
描述的语言的解析器
por(P1, P2) ->
fun (T) ->
case P1(T) of
{ok, R, T1} ->
{ok, {'or',1,R}, T1};
fail ->
case P2(T) of
{ok, R1, T1} ->
{ok, {'or',2,R1}, T1};
fail ->
fail
end
end
end.
最初的问题是解析语法 (a | b) & (c | d)
。以下代码解决了这个问题
grammar() ->
pand(
por(pconst(a), pconst(b)),
por(pconst(c), pconst(d))).
以下代码为语法添加了解析器接口
parse(List) ->
(grammar())(List).
可以按如下方式测试解析器
> funparse:parse([a,c]).
{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
> funparse:parse([a,d]).
{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
> funparse:parse([b,c]).
{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
> funparse:parse([b,d]).
{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
> funparse:parse([a,b]).
fail