退出交互模式:ctrl+c 然后敲a;halt().
命令后加休止符‘.’才能有效
%注释掉当前至行末的代码
编译erl代码c(ModuleName)
_表示任意变量
变量在它的上下文(作用域)中,只能赋值一次
寻求帮助:在bash下用erl -man xx
文件开头格式:
-module(ModuleName).
-export([FuncName1/ParaNum, ..]).
元子 erlang中的一种数据类型,以小写字母开头,只是一个简单的名字,其他什么也不是
元组 {}
列表 [] [First, Second | Rest] = [1,2,3,4,5,6] ===> First=>1 Second=>2 Rest=>[3,4,5,6]
->守卫,->之前的条件满足时测执行这个部分
if和case的守护字句后面要用分号,最后一个跟end挨着的字句不用标点.递归函数的出口函数末尾也需要用分号
if
xxx->
...;
true->
...
end.
case XX of
xxx->
...;
_->
...
end.
receive
xxx->
xxxxx;
yyy->
yyyyy
end.
receive
xxx->
xxxxx,
yyy->
yyyyy
after 5000->%毫秒
timeout codes
end.
list模块提供foreach(Func, [xx,xx..])函数和map(Func, [xx,xx..])(返回一个列表)函数
一个函数的输出是其内部最后一个东西的输出
io::format中~p和~w一样输出数据,不过它可以把一行很长的数据分成若干行,以使所有数据可见,还会检测列表中的可打印字符,并以字符串输出
内建函数BIFs
trunc(100.5)去除小数部分
Num rem 5 计算Num除以5的余数
atom_to_list
list_to_atom
integer_to_list
spawn
whereis(mess_client) 返回undefined或者其他值
并行编程
spawn(模块,导出的函数,参数列表)建立一个新的进程
register(some_atom, Pid) 给进程一个名字some_atom
Pid|RegisterName ! Message 给进程发送Message
self() 返回当前正在运行的进程ID,包含其节点信息
分布式编程
erl -sname my_name 启动的erlang系统ip域相同(在同一个机器上启动多个erlang系统)
erl -name ip@my_name 在不同的ip域启动erlang系统
spawn(NodeName, ModuleName, FuncName, ParamsList) 从另外一个节点上启动一个进程
node() 返回当前节点的名字
{ResigterName, NodeName}|Pid ! Message 给NodeName节点的RegisterName进程发送消息(Pid是self()或者spawn()返回的进程id信息,包含节点信息,所以不再需要节点名)
spawn_link(Node, Mod, Func, Args)启动远程节点的一个进程并创建连接到该进程
monitor_node(Node, Flag)如果Flag是true,这个函数将使调用(该函数)的进程可以监控节点Node。如果节点已经舍弃或者并不存在,调用的进程将收到一个{nodedown,Node}的消息。如果Flag是false,监控将被关闭
disconnect_node(Nodename)从节点Nodename断开
列表操作
lists:keymember(Name, 2, User_List)
lists:keysearch(From, 1, User_List)
lists:keydelete(From, 1, User_List)
进程
exit(normal).正常退出
exit(reson). 非正常退出
link(Other_Pid).建立进程间的双向连接.建立link的进程有一方非正常退出会导致其他的也退出,如果要不退出则捕获{'EXIT',FromPID,Reason}消息来进行处理
文件结构
-record(name_of_record,{field_name1, field_name2, field_name3, ......}). 记录,在.hrl文件中
-record(message_to,{to_name, message}). 等价于 {message_to, To_Name, Message}
#message_to{message="hello", to_name=fred) 将创建记录 {message_to, fred, "hello"}
-define(name, realname). 宏,在.hrl文件中
?MODULE 系统宏,当前Module的名称
-include("xx.hrl"). 在.erl文件开头,.hrl是erlang的头文件
OTP
组件(应用): Mnesia数据库开发 Debuger调试 标准库STDLIB
HELP
宏定义在hrl头文件中,erl文件include头文件之后还是无法使用那些宏
杂乱的记录》》》》》》》》》》》》》》》》》》》》
Erlang的比较运算符
op Description
== 等于
/= 不等于
=< 小于等于
< 小于
>= 大于等于
> 大于
=:= 精确的等于
=/= 精确的不等于
等于和精确等于的区别:
如果要比较两个数,如果两个数之间是不同的类型,比如float和int那么,==操作会首先把两个数字转换成相同的相同类型。举例:
1> 1==1.0.
true
2> 1=:=1.0.
false
所以一般推荐用精确等于去比较
比较运算符的大小级别:
number < atom < reference < fun < port < pid < tuple < list < bit string
3> 1 > a.
false
op Description Argument type
+ number
- number
+ number
- number
* number
/ 浮点数除法,结果是浮点数 number
bnot 一元not运算符 integer
div 整数除法,结果是整数 integer
rem 求玉树 integer
band and运算 integer
bor or运算 integer
bxor xor异或运算 integer
bsl 左移位操作 integer
bsr 右移位操作 integer
逻辑运算符
op Description
not 一元逻辑not
and 逻辑and
or 逻辑or
xor 逻辑xor
原子true 和false表示逻辑的”真”和”假”
此外,逻辑运算符还包括一个orelse 和andalso
原始的or和and是不带”短路运算”操作的,而orelse和andalso是带短路运算操作的。
短路运算举例
Express1 and Express2
Express1 andalso Express2
如果Express1 为假,and会继续判断Express2,然后整体判定为假,而andalso”短路”操作,直接判定整个表达式为假,从效率上来说,andalso会高一些
1. 一个函数以一些以分号隔开的子句组成,最后一条子句以句号结束,表示函数结束。每个子句都有一个函数头和函数体。函数头由函数名和随后的以括号括起来的模式组成,函数体则由一系列表达式(表达式以逗号分隔)组成。调用时,如果函数头中的模式与调用参数匹配成功的话,其对应的表达式就会进行运算。模式将按照它们在函数定义中的先后顺序进行匹配,匹配完一个子句后,不再匹配下一下了。
2. 模块可以在shell中用c(模块名)的方式进行编译,调用模块中的函数的方法是:模块名:函数(参数)。
3. 当对函数的调用不能匹配时,会抛出一个运行时错误。
4. 切换当前目录的方法:
(1)在erlang的shell中:cd("相对目录名或绝对目录名"). pwd()会返回当前目录。
(2)在erlang的安装目录下写一个名为.erlang的文件,erl的shell在启动时会前执行这个文件。文件中的内容如下:
io:format("consulting .erlang in ~p~n", [element(2, file:get_cwd())]). %% element(2, 元组)取元组的第2个元素。file:get_cwd() 获取当前目录名
c:cd("c:/work"). %% c:的意思应该是在shell中执行一个命令。
io:format("Now in:~p~n", [element(2, file:get_cwd())]). %% 显示切换后的当前目录。
5. erlang中的标点符号:
逗号(,)用来分隔函数调用、数据构造器以及模式中的参数。
句号(.)(后跟一个空白符号,否则可能是浮点数的小数点)用来在shell中分隔完整的函数和表达式。
分号(;)用来分隔子句,用到子句的情况:分段的函数定义、case语句、if语句、try...catch语句以及receive表达式。
无论何时,我们只要看到一组后面跟有表达式的模式,都会使用分号进行分隔。
6. 函数的目就是它所拥有的参数的数量。同名不同目的函数是两个完全不同的函数,除了它们的名字恰巧相同外。同名不同目的函数一般可以用做辅助函数。
7. fun是匿名函数。Z = fun(X) -> 2*X end. 这里Z相当于这个匿名函数的指针。这个指针可以付给另外一个变量,如:Double = Z. 调用这个函数可以Double(4).。
8. fun与正常的函数一样,可以有多个参数,也可以有多个子句。只是要在最后加一个end.。多个子句的fun函数只在第一个子名处写fun,以下的子句的fun省略,最后一个子句不写分号(;),如:
fun(X,Y) -> X*Y;
(a,Y) -> a
end.
9. fun函数可以做为参数传递给其它的函数(当然也包括fun函数本身),也可以做为函数的返回值。使用fun函数做为参数或返回fun函数的函数称为高阶函数。
10. lists:map(F, L) 返回一个列表,将列表L的每个元素应用函数F。lists:filter(P,L)返回一个列表,列表中的元素是列表L中满足P(l)为true的元素的集合。lists:member(X, L)判断X是否在列表L中。
11. 返回fun的例子:
Fruit = [apple, pear, orange].
MakeTest = fun(L) -> (fun(X) -> lists:member(X, L) end ) end. %%注意前一个end后面不有句号。
IsFruit = MakeTest(Fruit).
IsFruit(pear). %%返回true
IsFruit(dog). %%返回false
lists:filter(IsFruit, [dog, pear, apple, click]). %% 返回[pear, apple]
12. 返回fun的函数有点像c++中的函数模范,用它可以构造出很多个版本的函数来。
13. 自定义的for循环结构:
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I) | for(I+1, Max)].
14. -import(lists, [map/2, sum/1]. 导入lists模块中的map/2和sum/1函数,这样再应用时可以直接写map(...)和sum(...)了。
15. -export([total/1]). 将本模块中的total/1函数导出,这样total可以在本模块外部被调用。
16. 列表解析: [F(X) || X <- L]。例子:
[ 2*X || X <- L] %% 将列表L的元素*2,生成新的列表。
[{Name, 2*Number} || {Name, Number} <- Buy]
[shop:cost(A)*B || {A, B} <- Buy]
map(F, L) -> [F(X) || X <- L].
17. 列表解析的常见形式:[X || Qualifier1, Qualifier2, ...]
X是一个任意的表达式,每一个限定词可以是一个生成器,也可以是一个过滤器。
生成器: Pattern <- ListExpr, ListExpr必须是一个对列表项求值的表达式。
过滤器: 可以是一个返回true或false的函数,也可以是一个布尔表达式。
其实,生成器的Pattern也可以起到过滤器的作用,如:
[X || {a, X} <- [{a, 1}, {b, 2}, {c, 3}, {a, 4}, hello, "wow"]]. %% 返回值:[1,4]
相当于[X || {A, X} <- [{a, 1}, {b, 2}, {c, 3}, {a, 4}, hello, "wow"], A=:=a].
map18. A++B,是将列表B附加到列表A上生成一个新的列表,但效率不高。
A--B,是从列表A中删除与B中元素相同的所有元素,如果元素X在B中出现K次,则会从A中依次删除K个元素X。
19. lists:seq(M,N) 返回一个从M到N的整数组成的列表,包含M和N在内。 seq应该是sequence的缩写。
20. 算术表达式
1 +X
1 -X
2 X*Y
2 X/Y
2 bnot X 对X按位取反
2 X div Y X整除Y
2 X rem Y X除Y取余数
2 X band Y 对X和Y按位取与
3 X+Y
3 X-Y
3 X bor Y 对X和Y按位取或
3 X bxor Y 对X和Y按位进行异或
3 X bsl N 对X按位左移N位
3 X bsr N 对X按位右移N位
21. 断言(guard)是一种用于强化模式匹配功能的结构。
22. 在函数定义的头部使用断言时,必须以关键字when开头。如:
max(X, Y) when X > Y -> X;
man(X, Y) -> Y.
23. 可以在任何允许使用表达式的地方使用断言,当断言用于表达式时,它要么返回原子true(认为是求值成功),要么返回原子false(求值失败)。
24. 断言序列:
一组用逗号分隔的断言表达式表示的是and关系,即所以的断言为true,整个断言序列才为true。
一组用分号分隔的断言表达式表示的是or关系。
25. 不是所有合法的erlang表达式都可以做为断言表达式,只有下面的表达式才可以:
原子true 为什么要有true断言呢?这是因为可以在if表达式中做为catchall使用,相当于C语言中的if控制中的else,或switch中的default
其他常量(条件或绑定变量), 这些在断言表达式中都会被求值为false。
表3-2中的断言谓词或表3-3中的BIF(built-in function)
比较表达式
算术表达式
布尔表达式
短路布尔表达式 oralso和andalso(有点像C中的||,&&运算,先求前一个表达式的值,如果有必要再求后一个表达式的值)
26. 断言谓词:
is_atom(X)
is_binary(X)
is_constant(X)
is_float(X)
is_function(X)
is_function(X, N)
is_integer(X)
is_list(X)
is_number(X)
is_pid(X)
is_port(X)
is_reference(X)
is_tuple(X)
is_record(X, Tag)
is_record(X, Tag, N)
27. 断言BIF
abs(X) X的绝对值
element(N, X) 元组X的第N个元素
float(X) 将数字N转换为浮点数
hd(X) 列表X的头部
length(X) 列表X的长度
node() 当前节点
node(X) 进程X的节点
round(X) 将数字X转换为整数(四舍五入)
self() 当前进程的标识符
size(X) X的大小,X为元组或二进制数据
trunc(X) 将数字X转换为整数(截取)
tl(X) 列表X的尾部
28. 记录:将一个名称与元组中的元素一一对应起来的方法。
29. 记录的定义:
-record(Name, {key1=Default1, key2=Default2, key3, ...}). %% 其中Name是记录的名字,Default1和Default2分别是key1和key2的默认值。
记录的名字和键名必须是原子。
30. 记录一般放在erlang源代码文件或.hrl文件中,这些文件可以被其它的erlang源代码引用。
31. 在shell中引用记录用命令rr("filename.htl"). rr是read record的缩写。
释放记录用命令rf(记录名)。
32. 创建记录:X=#todo{} 创建一个记录,其中记录的键值是默认值。没有默认值的键默认会付值为原子undefined
X1=#todo{status=urgent, text="Fix errata in book"}
33. 复制记录:X2=X1#todo{status=done}.
34. 提取记录字段的值(两种方法):
#todo{who=W, text=Txt} = X2. %% 用模式匹配的方式
X2#todo.text.
35. 记录做为函数的参数进行模式匹配:
clear_status(#todo{status=S, who=W}=R) -> %% S和W分别匹配记录的status, who,而R匹配整个记录。
R#todo{status=finished} %% 注意:这里并不是修改了R,而是生成了一个新的记录返回。新的记录复制了R,同时将status修改为finished。
如果想匹配指定类型的记录,可以用断言的方式
clear_status(#todo{status=S, who=W}=R) when is_record(R, todo) -> R#todo{status=finished}.
36. 记录被释放后,原来为记录的变量就会变是一个普通的元组了。看来记录只是一个元组的显示界面而已。
37. 如果为每件事情都定义一个独立的函数子句不方便,可以使用case或if表达式。
38. case表达式:
case Expression of
Pattern1 [when Guard1] -> Expr_seq1;
Pattern2 [when Guard2] -> Expr_seq2;
... %% 最后一个不能加分号
end
首先计算Expression的值,然后将其值与Pattern1/Pattern2...进行模式匹配,如果没有匹配上,则抛出异常。
例子, filter(P, L)
filter(P, [H|T]) - >
case P(H) of
true -> [H|filter(P, T)];
false -> filter(P, T)
end;
filter(P, []) -> [].
39. if表达式:
if
Guard1 -> Expr_seq1;
Guard2 -> Expr_seq2;
... %% 最后一个不能加分号
end
一般情况下,if表达式的最后一个断言会是原子true,否则,如果所以断言都为false,则会抛出异常。
40. 尽量在一个列表的头部进行操作,尽量避免用到List++[H]这样的代码,除非List很短。通常要以自然顺序创建列表。规则如下:
(1)总是在列表头部添加元素。
(2)从一个输入列表的头部提取元素,然后把它们加在一个输出列表的头部。输出列表中的结果与输入列表的顺序相反。
(3)如果顺序至关重要,那么调用经过高度优化的函数list:reverse/1。 (这个函数是在erlang虚拟机中实现的,经过了高度的优化)。
(4)避免违反这些原则。
41. 如何在函数之外返回多于一个的列表呢?可以返回一个由多个列表组成的元组。
42. 累加器
odds_and_evens_acc(L) -> odds_and_evens_acc(L, [], []);
odds_and_evens_acc([H|T], Odds, Evens) ->
case H rem 2 of
0 -> odds_and_evens_acc(T, [H|Odds], Evens);
1 -> odds_and_evens_acc(T, Odds, [H|Evens)
end;
odds_and_evens_acc([], Odds, Evens) -> {lists:reverse(Odds), lists:reverse(Evens)}.
命令后加休止符‘.’才能有效
%注释掉当前至行末的代码
编译erl代码c(ModuleName)
_表示任意变量
变量在它的上下文(作用域)中,只能赋值一次
寻求帮助:在bash下用erl -man xx
文件开头格式:
-module(ModuleName).
-export([FuncName1/ParaNum, ..]).
元子 erlang中的一种数据类型,以小写字母开头,只是一个简单的名字,其他什么也不是
元组 {}
列表 [] [First, Second | Rest] = [1,2,3,4,5,6] ===> First=>1 Second=>2 Rest=>[3,4,5,6]
->守卫,->之前的条件满足时测执行这个部分
if和case的守护字句后面要用分号,最后一个跟end挨着的字句不用标点.递归函数的出口函数末尾也需要用分号
if
xxx->
...;
true->
...
end.
case XX of
xxx->
...;
_->
...
end.
receive
xxx->
xxxxx;
yyy->
yyyyy
end.
receive
xxx->
xxxxx,
yyy->
yyyyy
after 5000->%毫秒
timeout codes
end.
list模块提供foreach(Func, [xx,xx..])函数和map(Func, [xx,xx..])(返回一个列表)函数
一个函数的输出是其内部最后一个东西的输出
io::format中~p和~w一样输出数据,不过它可以把一行很长的数据分成若干行,以使所有数据可见,还会检测列表中的可打印字符,并以字符串输出
内建函数BIFs
trunc(100.5)去除小数部分
Num rem 5 计算Num除以5的余数
atom_to_list
list_to_atom
integer_to_list
spawn
whereis(mess_client) 返回undefined或者其他值
并行编程
spawn(模块,导出的函数,参数列表)建立一个新的进程
register(some_atom, Pid) 给进程一个名字some_atom
Pid|RegisterName ! Message 给进程发送Message
self() 返回当前正在运行的进程ID,包含其节点信息
分布式编程
erl -sname my_name 启动的erlang系统ip域相同(在同一个机器上启动多个erlang系统)
erl -name ip@my_name 在不同的ip域启动erlang系统
spawn(NodeName, ModuleName, FuncName, ParamsList) 从另外一个节点上启动一个进程
node() 返回当前节点的名字
{ResigterName, NodeName}|Pid ! Message 给NodeName节点的RegisterName进程发送消息(Pid是self()或者spawn()返回的进程id信息,包含节点信息,所以不再需要节点名)
spawn_link(Node, Mod, Func, Args)启动远程节点的一个进程并创建连接到该进程
monitor_node(Node, Flag)如果Flag是true,这个函数将使调用(该函数)的进程可以监控节点Node。如果节点已经舍弃或者并不存在,调用的进程将收到一个{nodedown,Node}的消息。如果Flag是false,监控将被关闭
disconnect_node(Nodename)从节点Nodename断开
列表操作
lists:keymember(Name, 2, User_List)
lists:keysearch(From, 1, User_List)
lists:keydelete(From, 1, User_List)
进程
exit(normal).正常退出
exit(reson). 非正常退出
link(Other_Pid).建立进程间的双向连接.建立link的进程有一方非正常退出会导致其他的也退出,如果要不退出则捕获{'EXIT',FromPID,Reason}消息来进行处理
文件结构
-record(name_of_record,{field_name1, field_name2, field_name3, ......}). 记录,在.hrl文件中
-record(message_to,{to_name, message}). 等价于 {message_to, To_Name, Message}
#message_to{message="hello", to_name=fred) 将创建记录 {message_to, fred, "hello"}
-define(name, realname). 宏,在.hrl文件中
?MODULE 系统宏,当前Module的名称
-include("xx.hrl"). 在.erl文件开头,.hrl是erlang的头文件
OTP
组件(应用): Mnesia数据库开发 Debuger调试 标准库STDLIB
HELP
宏定义在hrl头文件中,erl文件include头文件之后还是无法使用那些宏
杂乱的记录》》》》》》》》》》》》》》》》》》》》
Erlang的比较运算符
op Description
== 等于
/= 不等于
=< 小于等于
< 小于
>= 大于等于
> 大于
=:= 精确的等于
=/= 精确的不等于
等于和精确等于的区别:
如果要比较两个数,如果两个数之间是不同的类型,比如float和int那么,==操作会首先把两个数字转换成相同的相同类型。举例:
1> 1==1.0.
true
2> 1=:=1.0.
false
所以一般推荐用精确等于去比较
比较运算符的大小级别:
number < atom < reference < fun < port < pid < tuple < list < bit string
3> 1 > a.
false
op Description Argument type
+ number
- number
+ number
- number
* number
/ 浮点数除法,结果是浮点数 number
bnot 一元not运算符 integer
div 整数除法,结果是整数 integer
rem 求玉树 integer
band and运算 integer
bor or运算 integer
bxor xor异或运算 integer
bsl 左移位操作 integer
bsr 右移位操作 integer
逻辑运算符
op Description
not 一元逻辑not
and 逻辑and
or 逻辑or
xor 逻辑xor
原子true 和false表示逻辑的”真”和”假”
此外,逻辑运算符还包括一个orelse 和andalso
原始的or和and是不带”短路运算”操作的,而orelse和andalso是带短路运算操作的。
短路运算举例
Express1 and Express2
Express1 andalso Express2
如果Express1 为假,and会继续判断Express2,然后整体判定为假,而andalso”短路”操作,直接判定整个表达式为假,从效率上来说,andalso会高一些
1. 一个函数以一些以分号隔开的子句组成,最后一条子句以句号结束,表示函数结束。每个子句都有一个函数头和函数体。函数头由函数名和随后的以括号括起来的模式组成,函数体则由一系列表达式(表达式以逗号分隔)组成。调用时,如果函数头中的模式与调用参数匹配成功的话,其对应的表达式就会进行运算。模式将按照它们在函数定义中的先后顺序进行匹配,匹配完一个子句后,不再匹配下一下了。
2. 模块可以在shell中用c(模块名)的方式进行编译,调用模块中的函数的方法是:模块名:函数(参数)。
3. 当对函数的调用不能匹配时,会抛出一个运行时错误。
4. 切换当前目录的方法:
(1)在erlang的shell中:cd("相对目录名或绝对目录名"). pwd()会返回当前目录。
(2)在erlang的安装目录下写一个名为.erlang的文件,erl的shell在启动时会前执行这个文件。文件中的内容如下:
io:format("consulting .erlang in ~p~n", [element(2, file:get_cwd())]). %% element(2, 元组)取元组的第2个元素。file:get_cwd() 获取当前目录名
c:cd("c:/work"). %% c:的意思应该是在shell中执行一个命令。
io:format("Now in:~p~n", [element(2, file:get_cwd())]). %% 显示切换后的当前目录。
5. erlang中的标点符号:
逗号(,)用来分隔函数调用、数据构造器以及模式中的参数。
句号(.)(后跟一个空白符号,否则可能是浮点数的小数点)用来在shell中分隔完整的函数和表达式。
分号(;)用来分隔子句,用到子句的情况:分段的函数定义、case语句、if语句、try...catch语句以及receive表达式。
无论何时,我们只要看到一组后面跟有表达式的模式,都会使用分号进行分隔。
6. 函数的目就是它所拥有的参数的数量。同名不同目的函数是两个完全不同的函数,除了它们的名字恰巧相同外。同名不同目的函数一般可以用做辅助函数。
7. fun是匿名函数。Z = fun(X) -> 2*X end. 这里Z相当于这个匿名函数的指针。这个指针可以付给另外一个变量,如:Double = Z. 调用这个函数可以Double(4).。
8. fun与正常的函数一样,可以有多个参数,也可以有多个子句。只是要在最后加一个end.。多个子句的fun函数只在第一个子名处写fun,以下的子句的fun省略,最后一个子句不写分号(;),如:
fun(X,Y) -> X*Y;
(a,Y) -> a
end.
9. fun函数可以做为参数传递给其它的函数(当然也包括fun函数本身),也可以做为函数的返回值。使用fun函数做为参数或返回fun函数的函数称为高阶函数。
10. lists:map(F, L) 返回一个列表,将列表L的每个元素应用函数F。lists:filter(P,L)返回一个列表,列表中的元素是列表L中满足P(l)为true的元素的集合。lists:member(X, L)判断X是否在列表L中。
11. 返回fun的例子:
Fruit = [apple, pear, orange].
MakeTest = fun(L) -> (fun(X) -> lists:member(X, L) end ) end. %%注意前一个end后面不有句号。
IsFruit = MakeTest(Fruit).
IsFruit(pear). %%返回true
IsFruit(dog). %%返回false
lists:filter(IsFruit, [dog, pear, apple, click]). %% 返回[pear, apple]
12. 返回fun的函数有点像c++中的函数模范,用它可以构造出很多个版本的函数来。
13. 自定义的for循环结构:
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I) | for(I+1, Max)].
14. -import(lists, [map/2, sum/1]. 导入lists模块中的map/2和sum/1函数,这样再应用时可以直接写map(...)和sum(...)了。
15. -export([total/1]). 将本模块中的total/1函数导出,这样total可以在本模块外部被调用。
16. 列表解析: [F(X) || X <- L]。例子:
[ 2*X || X <- L] %% 将列表L的元素*2,生成新的列表。
[{Name, 2*Number} || {Name, Number} <- Buy]
[shop:cost(A)*B || {A, B} <- Buy]
map(F, L) -> [F(X) || X <- L].
17. 列表解析的常见形式:[X || Qualifier1, Qualifier2, ...]
X是一个任意的表达式,每一个限定词可以是一个生成器,也可以是一个过滤器。
生成器: Pattern <- ListExpr, ListExpr必须是一个对列表项求值的表达式。
过滤器: 可以是一个返回true或false的函数,也可以是一个布尔表达式。
其实,生成器的Pattern也可以起到过滤器的作用,如:
[X || {a, X} <- [{a, 1}, {b, 2}, {c, 3}, {a, 4}, hello, "wow"]]. %% 返回值:[1,4]
相当于[X || {A, X} <- [{a, 1}, {b, 2}, {c, 3}, {a, 4}, hello, "wow"], A=:=a].
map18. A++B,是将列表B附加到列表A上生成一个新的列表,但效率不高。
A--B,是从列表A中删除与B中元素相同的所有元素,如果元素X在B中出现K次,则会从A中依次删除K个元素X。
19. lists:seq(M,N) 返回一个从M到N的整数组成的列表,包含M和N在内。 seq应该是sequence的缩写。
20. 算术表达式
1 +X
1 -X
2 X*Y
2 X/Y
2 bnot X 对X按位取反
2 X div Y X整除Y
2 X rem Y X除Y取余数
2 X band Y 对X和Y按位取与
3 X+Y
3 X-Y
3 X bor Y 对X和Y按位取或
3 X bxor Y 对X和Y按位进行异或
3 X bsl N 对X按位左移N位
3 X bsr N 对X按位右移N位
21. 断言(guard)是一种用于强化模式匹配功能的结构。
22. 在函数定义的头部使用断言时,必须以关键字when开头。如:
max(X, Y) when X > Y -> X;
man(X, Y) -> Y.
23. 可以在任何允许使用表达式的地方使用断言,当断言用于表达式时,它要么返回原子true(认为是求值成功),要么返回原子false(求值失败)。
24. 断言序列:
一组用逗号分隔的断言表达式表示的是and关系,即所以的断言为true,整个断言序列才为true。
一组用分号分隔的断言表达式表示的是or关系。
25. 不是所有合法的erlang表达式都可以做为断言表达式,只有下面的表达式才可以:
原子true 为什么要有true断言呢?这是因为可以在if表达式中做为catchall使用,相当于C语言中的if控制中的else,或switch中的default
其他常量(条件或绑定变量), 这些在断言表达式中都会被求值为false。
表3-2中的断言谓词或表3-3中的BIF(built-in function)
比较表达式
算术表达式
布尔表达式
短路布尔表达式 oralso和andalso(有点像C中的||,&&运算,先求前一个表达式的值,如果有必要再求后一个表达式的值)
26. 断言谓词:
is_atom(X)
is_binary(X)
is_constant(X)
is_float(X)
is_function(X)
is_function(X, N)
is_integer(X)
is_list(X)
is_number(X)
is_pid(X)
is_port(X)
is_reference(X)
is_tuple(X)
is_record(X, Tag)
is_record(X, Tag, N)
27. 断言BIF
abs(X) X的绝对值
element(N, X) 元组X的第N个元素
float(X) 将数字N转换为浮点数
hd(X) 列表X的头部
length(X) 列表X的长度
node() 当前节点
node(X) 进程X的节点
round(X) 将数字X转换为整数(四舍五入)
self() 当前进程的标识符
size(X) X的大小,X为元组或二进制数据
trunc(X) 将数字X转换为整数(截取)
tl(X) 列表X的尾部
28. 记录:将一个名称与元组中的元素一一对应起来的方法。
29. 记录的定义:
-record(Name, {key1=Default1, key2=Default2, key3, ...}). %% 其中Name是记录的名字,Default1和Default2分别是key1和key2的默认值。
记录的名字和键名必须是原子。
30. 记录一般放在erlang源代码文件或.hrl文件中,这些文件可以被其它的erlang源代码引用。
31. 在shell中引用记录用命令rr("filename.htl"). rr是read record的缩写。
释放记录用命令rf(记录名)。
32. 创建记录:X=#todo{} 创建一个记录,其中记录的键值是默认值。没有默认值的键默认会付值为原子undefined
X1=#todo{status=urgent, text="Fix errata in book"}
33. 复制记录:X2=X1#todo{status=done}.
34. 提取记录字段的值(两种方法):
#todo{who=W, text=Txt} = X2. %% 用模式匹配的方式
X2#todo.text.
35. 记录做为函数的参数进行模式匹配:
clear_status(#todo{status=S, who=W}=R) -> %% S和W分别匹配记录的status, who,而R匹配整个记录。
R#todo{status=finished} %% 注意:这里并不是修改了R,而是生成了一个新的记录返回。新的记录复制了R,同时将status修改为finished。
如果想匹配指定类型的记录,可以用断言的方式
clear_status(#todo{status=S, who=W}=R) when is_record(R, todo) -> R#todo{status=finished}.
36. 记录被释放后,原来为记录的变量就会变是一个普通的元组了。看来记录只是一个元组的显示界面而已。
37. 如果为每件事情都定义一个独立的函数子句不方便,可以使用case或if表达式。
38. case表达式:
case Expression of
Pattern1 [when Guard1] -> Expr_seq1;
Pattern2 [when Guard2] -> Expr_seq2;
... %% 最后一个不能加分号
end
首先计算Expression的值,然后将其值与Pattern1/Pattern2...进行模式匹配,如果没有匹配上,则抛出异常。
例子, filter(P, L)
filter(P, [H|T]) - >
case P(H) of
true -> [H|filter(P, T)];
false -> filter(P, T)
end;
filter(P, []) -> [].
39. if表达式:
if
Guard1 -> Expr_seq1;
Guard2 -> Expr_seq2;
... %% 最后一个不能加分号
end
一般情况下,if表达式的最后一个断言会是原子true,否则,如果所以断言都为false,则会抛出异常。
40. 尽量在一个列表的头部进行操作,尽量避免用到List++[H]这样的代码,除非List很短。通常要以自然顺序创建列表。规则如下:
(1)总是在列表头部添加元素。
(2)从一个输入列表的头部提取元素,然后把它们加在一个输出列表的头部。输出列表中的结果与输入列表的顺序相反。
(3)如果顺序至关重要,那么调用经过高度优化的函数list:reverse/1。 (这个函数是在erlang虚拟机中实现的,经过了高度的优化)。
(4)避免违反这些原则。
41. 如何在函数之外返回多于一个的列表呢?可以返回一个由多个列表组成的元组。
42. 累加器
odds_and_evens_acc(L) -> odds_and_evens_acc(L, [], []);
odds_and_evens_acc([H|T], Odds, Evens) ->
case H rem 2 of
0 -> odds_and_evens_acc(T, [H|Odds], Evens);
1 -> odds_and_evens_acc(T, Odds, [H|Evens)
end;
odds_and_evens_acc([], Odds, Evens) -> {lists:reverse(Odds), lists:reverse(Evens)}.