非常教程

Erlang 20参考手册

指南:编程举例 | Guide: Programming examples

2. Funs

2.1地图

以下函数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([])    -> [].

功能doubleadd_one具有相似的结构。这可以通过编写一个map表达这种相似性的函数来使用:

map(F, [H|T]) -> [F(H)|map(F, T)];
map(F, [])    -> [].

功能doubleadd_one现在可以用map详情如下:

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

抽象出许多不同程序的共同特征的过程称为程序抽象。程序抽象可用于编写几个具有相似结构的不同功能,但在一些细节上有所不同。这如下完成:

  • 第一步。编写一个表示这些函数的共同特性的函数。
  • 第二步。参数化作为参数传递给公共函数的函数的差异。

2.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)打电话Fun(X)对于每个元素XL中定义元素的顺序进行处理。L...map不定义其元素的处理顺序。

2.3幽默句法

Funs使用以下语法编写(请参阅Fun Expressions完整说明):

F = fun (Arg1, Arg2, ... ArgN) ->
        ...
    end

这将创建N参数的匿名函数并将其绑定到变量F

FunctionName使用以下语法可以将另一个函数写入同一模块中作为参数传递:

F = fun FunctionName/Arity

使用这种形式的函数引用,不需要从模块导出所引用的函数。

还可以引用在不同模块中定义的函数,语法如下:

F = fun Module:FunctionName/Arity

在这种情况下,该函数必须从所述模块导出。

以下程序说明了创建Funs的不同方法:

-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.

有趣的F可以用下面的语法来评估:

F(Arg1, Arg2, ..., Argn)

若要检查某个术语是否有趣,请使用该测试。is_function/1在警卫里。

例子:

f(F, Args) when is_function(F) ->
   apply(F, Args);
f(N, _) when is_integer(N) ->
   N.

Funs是一个独特的类型。该内建函数erlang:fun_info/1,2可用于检索一个有趣的信息,BIF erlang:fun_to_list/1返回一个有趣的文字表述。该check_process_code/2BIF返回true如果过程包含依赖于旧版本的模块的玩意儿。

2.4变量绑定

Funs中出现的变量的作用域规则如下:

  • 所有出现在乐趣头上的变量都被假定为“新鲜”变量。
  • 在乐趣之前定义的变量,以及在乐趣中的函数调用或守卫测试中发生的变量都具有它们超出乐趣的值。
  • 变量不能从乐趣中导出。

以下例子说明了这些规则:

print_list(File, List) ->
    {ok, Stream} = file:open(File, write),
    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    file:close(Stream).

这里,变量X,定义在头上的乐趣,是一个新的变量。变量Stream,它在乐趣中使用,它从file:open排队。

由于在乐趣中出现的任何变量都被认为是一个新变量,因此编写如下代码同样有效:

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这并不明智,因为有趣的代码不能引用变量。File,它是在乐趣之外定义的。编译此示例将提供以下诊断:

./FileName.erl:Line: Warning: variable 'File' 
      shadowed in 'fun'

这表明变量File,它在乐趣中定义,与变量发生冲突。File,这是在乐趣之外定义的。

将变量导入到FIN中的规则的结果是,某些模式匹配操作必须移动到守护表达式中,并且不能写入乐趣的头中。例如,如果您打算使用F当其参数的值为Y*

f(...) ->
    Y = ...
    map(fun(X) when X == Y ->
             ;
           (_) ->
             ...
        end, ...)
    ...

而不是编写以下代码:

f(...) ->
    Y = ...
    map(fun(Y) ->
             ;
           (_) ->
             ...
        end, ...)
    ...

2.5乐趣和模块列表

以下示例显示了与Erlang shell的对话。所讨论的所有高阶函数都从模块中导出lists

地图

map接受一个参数的函数和一个术语列表:

map(F, [H|T]) -> [F(H)|map(F, T)];
map(F, [])    -> [].

它返回通过将函数应用于列表中的每个参数而获得的列表。

当在shell中定义新的乐趣时,乐趣的值被打印为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取谓词P一个论点和一个术语清单:

any(Pred, [H|T]) ->
    case Pred(H) of
        true  ->  true;
        false ->  any(Pred, T)
    end;
any(Pred, []) ->
    false.

谓词是返回一个函数truefalseanytrue如果有一个术语X列表,从而P(X)true

谓词Big(X)定义,即true如果它的论点大于10:

> 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

allany*

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接受一个参数的函数和一个术语列表:

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接受两个参数的函数,一个累加器和一个列表:

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

foldl像一个while在命令式语言中循环:

L =  ["I","like","Erlang"],
Sum = 0,
while( L != []){
    Sum += length(head(L)),
    L = tail(L)
end

mapfoldl

mapfoldl同时在列表上映射和折叠:

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 接受一个参数和一个列表的谓词并返回列表中满足谓词的所有元素:

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)是列表之间的差L1L2,所述代码能够如下写为:

diff(L1, L2) -> 
    filter(fun(X) -> not member(X, L2) end, L1).

这给出了L1中没有包含在L2中的所有元素的列表。

列表的AND相交L1并且L2也容易定义:

intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

takewhile

takewhile(P, L)取元素X从名单上L只要谓词P(X)是真的:

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]

落差

dropwhiletakewhile以下内容的补充:

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(P, L)拆分列表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]}

2.6 Funs返回Funs

到目前为止,只有以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是一棵解析树。
  • Toks1Tree包含正确解析的结构后遇到的符号的尾部。

不成功的解析返回fail

下面的示例演示了一个简单的函数解析器,它解析语法:

(a | b) & (c | d)

下面的代码定义了一个函数pconst(X)在模块中funparse,它返回解析令牌列表的乐趣:

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

接着,两个高阶函数pandpor定义。它们结合了原始分析器来生成更复杂的分析器。

第一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.

给定一个解析器P1文法G1,以及解析器P2对语法G2pand(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

指南:编程举例 | Guide: Programming examples相关

Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1