1.Erlang是一门函数式的编程语言,Erlang突出的特点是函数定义。一个真正的函数式的数据类型是funs。它们可以作为参数传递给其它函数,而且可以存储在像元组和记录类型的数据结构中或作为消息发送给其它进程。最重要的是,它们可以作为其它函数的结果,以至于函数可以作为数据传递,可以在程序中动态创建,而不仅仅指静态定义的函数,这可以让你写出简明、抽象、可再次使用的函数,而且它可以通过特定的行为进行参数化,即作为函数的参数“包装起来”,结果就是,代码不但变得更紧凑,而且更容易编写、理解和维护。
2.列表解析是另一个强大的结构,其根源在于函数式编程,列表解析允许生成列表,并把它们合并起来,然后基于一系列谓语进行筛选结果。其结果是一个由生成器产生的谓词评价为true的元素列表。就像funs,列表解析可以产生紧凑强大的代码,从而提高程序员的效率。
3.二进制是另一种Erlang的数据类型,虽然与函数式编程没有直接关联,但是在Erlang中它也有不小的影响。二进制只不过是一个1和0的序列,是一块存储在内存中的没有类型化的内容,所有套接字和端口通信都是基于二进制的,就像所有文件相关的I/O。在Erlang中使用二进制的威力在于位级别的模式匹配,这样使用很少的努力和代码提取相关的位和字节。这使得Erlang非常适合处理协议栈和与国际协议相关的传输,主要是编码和解码消息帧,只需要很少的代码就可以把它们作为协议栈处理的结果发送或接收。
4.最后引入了数据类型,其中的元素通常称为refs,可以在分布式环境中给你提供跨进程使用的独特标记。特别是,我们使用引用数据值进行比较,而其中许多是跟消息有关的。
Funs和高级编程
1.绑定变量的fun:
Bump=fun(Int)->Int+1 end.
这个fun需要一个变量作为参数,绑定它到变量Int并把它的数值增加1,可以通过在其后的括号中带上它的参数来调用fun,就好像调用函数一样,如果给它分配了一个变量,可以使用它的名称:
Bump(10).
或者,可以直接调用:
(fun(Int)->Int+1 end)(9).
一个fun就是一个函数,但不能使用模块,函数名称和元数来唯一识别它,而是使用它绑定的变量或者它的定义来确定。
函数作为参数
1.列表中最常见的操作之一是访问每个元素,并以某种方式将其转换。例如,下面的代码把一个数字列表中所有的元素都乘以2,并颠倒每一个列表中的元素:
doubleAll([])-> revAll([])->
[]; [].
doubleAll([X|Xs])-> revAll([X|Xs])->
[X*2|doubleAll(Xs)]. [reverse(X)|revAll(Xs)].
这两个例子的不同之处是影响了元素X转换方式,一个函数可以捕获这种转换,给出一个map函数,其第一个参数F是应用到列表中每个元素的函数:
map(F,[])->
[];
map(F,[X|Xs])->
[F(X)|map(F,Xs)].
2.另一种常见的列表操作是过滤拥有特别属性的元素,例如,偶数或者回文的列表:
evens([])-> palins([])->
[]; [];
evens([X|Xs])-> palins([X|Xs])->
case X rem 2 ==0 of case palins(X) of
true-> true->
[X|evens(Xs)]; [X|palins(Xs)];
_-> _->
evens(Xs) palins(Xs)
end. end.
palin/1在模块hof1中是如此定义的:
palin(X)->X==reverse(X).
filter函数把这种“过滤”行为作为一个定义包装起来,其中函数P包含了要测试的属性:如果X有此属性,P(X)会返回true,否则返回false:
filter(P,[])->
[];
filter(P,[X|Xs])->
case P(X) of
true->
[X|filter(P,Xs)];
_->
filter(P,Xs)
end.
像P(X)返回true或者false的函数称为谓语。
编写函数:函数表达式
1.编写函数定义的时候:必须给出函数的参数,可以使用模式匹配来区分不同的情况,并在最后一个被执行的表达式中返回结果。除了给出函数名字,一个fun表达式可以做同样的事情。
参数双倍化的函数如下:
fun(X)->X*2 end.
两个数字相加的函数如下:
fun(X,Y)->X+Y end.
得到一个列表的表头(如果为空则为null)的函数如下:
fun([])->null
([X|_])->X
end.
2.表达式需要单独使用fun...end围起来而已,有时会看到end后面紧跟句点。这是因为点号表示定义的结束或者终端输入行的结束。它不是函数自身表达式的一部分。很多情况下使用的语法和case类似:关键字fun不会在每个不同的模式匹配情况之前出现。但要记住,参数使用括号括起来,就如(...),即使是只有一个变量作为参数的情况也是如此。
通过使用函数表达式,可以使用map和filter来定义doubleAll和palins了:
doubleAll(Xs)->
map(fun(X)->X*2 end,Xs).
palins(Xs)->
filter(fun(X)->X==reverse(X) end,Xs).
在每一种情况下,可以看到函数表达式准确地“封装”了特殊行为映射或者过滤属性。
函数表达式也可以封装边界效应,例如:
fun(X)->io:format("Element:~p~n",[X]) end.
函数将打印它的参数消息到标准输出。
fun(X)->Pid!X end.
函数将它的参数作为消息发送到Pid。
3.把一个fun作为参数的函数称为高阶函数。
函数作为结果
例如:
sendTo(Pid)->
fun(X)->
Pid!X
end.
案例:
times(X)->
fun(Y)->X*Y end.
Times=times(3).返回一个函数。
(times(3))(2):返回6。
Times(2):返回6。
利用已定义的函数
1.使用那些已经定义作为其它函数的参数,或者作为变量的值的函数。在一个模块M中,元数为n局部函数F可以表示为fun F/n。在这个模块之外,可以把它标记为fun M:F/n。
2.fun foo/2只是表示法fun(A1,A2)->foo(A1,A2) end的语法。完全限定法是fun M:F/n。
3.请记住,当把fun传递到Erlang其它节点的时候,如果局部以及全局函数调用是在fun内完成的,那么定义这些调用函数定义所在的模块必须存在于远程Erlang节点的代码搜索路径中。
函数和变量
1.所有在fun表达式中引入的变量被认为是新变量,所以它们会遮盖任何一个已经存在的且具有相同名称的变量,这意味着不能再访问在fun表达式内被遮盖的变量,而退出fun的时候它们仍然可以访问。所有其它变量可以在fun内访问到,例如:
foo()->
X=2,
Bump=fun(X)->X+1 end,
Bump(10).
funs:foo():结果为11,因为X被覆盖了。
bar()->
X=3,
Add=fun(Y)->X+Y end,
Add(10).
funs:bar():结果为13,因为在fun中没有X。
预定义高阶函数
1.lists模块中包含一个高阶函数的集合,也就是说,函数把fun作为参数并且把它们应用到一个列表,这些高阶函数允许隐藏函数调用中的递归模式,并隔离所有的边界效应和在fun中对列表元素的操作。
2.lists模块中定义的高阶函数递归模式:
all(Predicate,List):如果Predicate,也就是一个返回一个布尔值的fun,当把它应用到列表中的每个元素上返回值都是true的时候返回true,否则返回false。
any(Predicate,List):如果谓语列表中的任何一个元素对Predicate的返回值为true则返回true,否则返回false。
dropwhile(Predicate,List):如果去除列表的头部和递归尾部Predicate,则返回true。
filter(Predicate,List):移除列表中Predicate是false的所有元素,返回一个剩余元素的列表。
foldl(Fun,Accumulator,List):带有两个参数的函数Fun,这些参数是来自于List和Accumulator的一个元素,fun返回新的Accumulator,一旦列表已经遍历完毕,这也是该调用的返回值。与它的姐妹lists:foldr/3功能不同,lists:foldl/3由左到右以尾递归方式遍历列表。
map(Fun,List):带一个Fun参数,它应用于列表中的每个元素。它返回一个包含fun应用结果的列表。
partition(Predicate,List):把一个列表分成两个列表,其中一个包含Predicate返回true的元素,另一个列表包含Predicate返回false的元素。
延迟求值和列表
1.一个适当或格式正确的列表要么是一个空表,要么其头部是一个元素而尾部是一个适当或者格式正确的列表的列表。Erlang的求值机制意味着当一个列表传递给一个函数,在函数本身执行之前该列表会被完全求值。其它函数式的语言,特别是Haskell,实现的是按需求驱动(或延迟)求值。在延迟求值的情况下,参数只有在必要的时候才在函数体内求值;对于数据结构,如列表,只有那些有需要的部分才会求值。
2.在Erlang中建立一个延迟列表是可能的,要做到这一点,可以使用这样的列表,尾部是一个fun,它返回一个新的头部和一个递归fun。这将避免生成一个大的列表,然后遍历它。相反,可以减少内存的使用并只在需要的时候生成后续的值。
列表解析
1.典型的列表操作是映射---应用一个函数到列表中的每一个元素,还有过滤---选择一个列表中具有特定属性的元素。通常这些方法一起使用,并且列表解析法提供了强大而简洁的方式来编写列表构造。
通用列表解析
1.通常来说,一个列表解析有三个组成部分:
[Expression||Generators,Guards,Generators,Guards,...]
生成器:
生成器的形式为Pattern<-List,其中Pattern是一种与List表达式中的元素相匹配的模式,符号<-可以理解为来自,它像数学符号"E",表达意思是"一个元素来自"。
保护元:
保护元就像函数定义中的保护元,其结果是true或者false,保护元中的变量是指那些在保护元左边出现的发生器(任何其它变量在外部级别定义)。
表达式:
表达式会指结果元素的形式。模式匹配执行两个任务,正如它在函数定义中的一样:它允许一个复杂的元素(如元组)匹配成员组成部分,而且它只选择那些和模式相匹配的元素。
二进制类型和反序列化
有时大量的结构化数据必须在计算机之间转移或存储。应该如何使协议确保数据能够尽可能快速和高效地产生和传播那?答案是利用一切可能的储存位,字中的每个位尽量包含可能多的信息。Erlang的二进制类型为操作二进制数据结构提供了一个模式匹配表示法,使这类较低级别的编程更简单和可靠,而且比使用元组或列表更节省空间。
二进制类型
1.二进制类型是指向一个原始、无类型的内存块的索引。最初它被Erlang运行时系统用作通过网络下载代码,但很快就应用在一个更通用的基于套接字的通信设置中。通过内置函数提供了编码、解码和操作二进制的功能,二进制类型能更有效的传输大量数据。
2.内置函数term_to_binary/1和binary_to_term/1把项元编码和解码为二进制类型,即字节序列。函数is_binary/1是一个用来测试二进制类型的保护元,当处理八进制的字节流时,即整数列表,可以使用list_to_binary/1和binary_to_list/1。
2.注意:最好使用term_to_binary把在单一二进制中的一连串的元素编码为一个单一的列表。把元素单独编码,使用函数list_to_binary连接结果,然后通过解码给出意外结果。所以要想把一个列表中的元素进行解码,首先需要将它进行分割,然后再分别对它们进行解码。
位语法
1.位语法允许把二进制看做一些段,它是位的序列但不必是字节(不必以字节边界对齐)。使用术语bitstring来表示任意长度的位序列,而术语binary是指字符串,它的长度是可被8整除,所以可以看做一个字节序列。
2.可以使用下面的位语法来构建Bin:
Bin=<<E1,E2,...,En>>
可以这样进行模式匹配:
<<E1,E2,...,En>>=Bin
3.位语法的实例:
Bin1=<<1,2,3>>:结果是<<1,2,3>>
binary_to_list(Bin1):结果是[1,2,3]
<<E,F>>=Bin1:结果是exception error:no match of right hand side value <<1,2,3>>
<<E,F,G>>=Bin1:结果是<<1,2,3>>
E:结果是1
[B|Bs]=binary_to_list(Bin1):结果是[1,2,3]
Bin2=list_to_binary(Bs):结果是[2,3]
4.二进制真正的长处在于,Bin的每个表达式都可以通过一个size和/或者type表达式来进行限定:
Expr:Size/Type
这些限定允许对数字格式进行精确控制,包括整数和浮点数,并意味着位程序可以当做高层次的协议的规范进行阅读,而不是协议的低级别(和不透明)实现。
Size:指定的大小以位为单位,一个整数的默认大小为8,一个浮点数的默认大小为64(8字节)。
Type:类型说明符列表,并以连字符分割,类型说明符可以是以下内容:
type:有效的类型是整数、浮点数、二进制、字节、位和位串。
sign:有效的sign包括signed和unsigned(默认值)。在signed的情况下,第一位确定sign:0位正数,1为负数。
endian值:endian值取决于CPU。endian值可以是big(默认)、little和native。在big endian情况下,第一个字节表示最低位;在little endian情况下,第一位表示最高位。只有当在不同的CPU架构中传输二进制时才需要使用endian值。如果希望endian在运行时确定,则使用native。
指定单位:条目使用位的数目是Val*N,其中N是该值的大小,位(bit)和位串(bitstring)缺省的单位是1;对于二进制和字节则是8.
按位模式匹配
1.匹配二进制的时候可以使用相同的语法,特别是在模式匹配中使用size和type。当类型省略的时候,默认类型是一个整数,例如:
A=1:结果是1.
Bin= <<A,17,42:16>>:结果是<<1,17,0,42>>
<<D:16,E,F/binary>>=Bin:结果是<<1,17,0,42>>
[D,E,F]:结果是[273,0,<<"*">>]
2.一个二进制解包的方式和构造的方式完全不同,这是复杂协议处理的数据机制之一,因此可以对帧进行解码和编码,并且Erlang使这些变得很简单:
Frame = <<1,3,0,0,1,0,0,0>>结果为<<1,3,0,0,1,0,0,0>>
<<Type,Size,Bin:Size/binary-unit:8,_/binary>>=Frame结果为<<1,3,0,0,1,0,0,0>>
Type结果为1
Size结果为3
Bin结果为<<0,0,1>>
计算方法是:因为Type占用一个字节,所以值为1,Size占用一个字节,所以值为3,因为Bin:Size占用三个字节,所以值为<<0,0,1>>,剩下的为最后的。
3.在二进制模式匹配中,有一些可能的陷阱:
系统不会理解表达式B=<<1>>,因为它会被读作B =<<1>>,所以重要的是永远把符号<<和=用一个空格隔开。
系统不会明白表达式<<X+1:8>>,诀窍是给算术表达式加上括号<<(X+1):8>>
<<X:7/binary,Y:1/binary>>永远不会匹配成功,因为每一个二进制序列在模式匹配中的长度必须是8的倍数。
不能使用一个未分配的变量做为一个片段的大小,如同在函数定义中:foo(N,<<X:N,...>>)->...。这里需要使用一个已经定义的值,但是,如果变量已定义,那么使用它来指定段的大小是可以的。
位串解析
1.列表解析表示法[...||X->List,test,...]是一种有力的方式,可以用它来描述通过"生成和测试"方法从其它列表中生成的列表,位串内涵符号为位串实现一个类似的作用,例如:
<< <<bnot(X):1>>||<<X:1>> <= <<42:6>> >>结果为<<21:6>>.
2.位串解析由<<...||...>>分割,而且<=用于生成器(而不是<-)。最重要的是,所有的位串实体都封装在<<...>>中,在前面的例子中,变量X是位变量,将以此分配到位101010(42的二进制格式中),它们中的每一个在输出时由bnot取反,得到字符串010101,从而得到21的二进制表示。
3.位串内涵的原理与列表相同:模式匹配语法用于生成器(在<=符号的左侧),结果可以用位语法描述。
位运算符
Erlang中的位级的运算符可用于整数,而结果也返回整数:
bond:按位与
bor:按位或
bxor:按位异或
bnot:按位非
bsl:按位左移,第二个参数给出移动位数
bsr:按位右移,第二个参数给出移动位数
序列化
1.一个用来序列化数据结构的方法是把这个结构格式良好的打印为一个完全置于括号内的字符串,通过解析可以反序列化这样的字符串,但这既不是一个有效率的存储形式,也不是一个有效率的反序列化机制。
2.当序列化结构的时候,可以知道所产生的的表达式的大小,因此,可以把这些信息集成到序列化中从而避免进行结构解析。最初的时候,可以把此结构转换成一个流,其中在每个子树之前记录下树的表达式的大小。例如:
[11,8,2,cat,5,2,dog,2,enum,2,fish],其中11是整个表达式的大小,8是列表[8,2,cat,5,2,dog,2,enum]的长度,它代表顶级节点的左子树。
然而,在序列格式里面有多余的信息,如果有一个段代表一个节点的子树,它足以推断出左子树的大小,而右子树由余下的给出。
引用
1.可以使用内置函数make_ref()创建引用,它在一个节点的生命周期内(几乎)是唯一的,它的值在2的28次方调用之后才会重复,它们对一个节点来说也是唯一的,所以两个不同的节点的两个引用永远不可能是相同的。
2.可以比较引用是否相等,而且通过使用一个索引标记一个消息,而后在回复中返回同一个索引,用这种方式可以在一个协议中匹配调用和响应。