3.1 模块
模块文件以.erl为后缀名, 编译后的文件后缀名为.beam。
erl文件示例
%% 模块名 与文件名相同
-module(geometry).
%% 函数定义 名称及参数
-export([area/1]).
%% 函数实现 字句之间用 ; 分隔
area({rectangle, Width, Ht}) ->Width * Ht;
area({circle, R}) ->3.14159 * R * R.
执行示例
# 编译
1> c(geometry).
{ok,geometry}
# 调用函数
2> geometry:area({rectangle, 10, 5}).
50
3> geometry:area({circle, 1.4}).
6.157516399999999
3.2 购物系统–进阶篇
-module(shop1).
-export([total/1]).
%% 采用递归的方式将列表中的各项商品金额相加得到总金额
total([{What, N} | T]) ->
shop:cost(What) * N + total(T);
total([]) ->0.
3.3 同名不同目的函数
-module(lib_misc).
-export([sum/1]).
-export([sum/2]).
%% 函数的目是指其参数数量
%% 同一模块中的同名不同目函数类似与java的重载
%% 但这里的同名只是为了便于理解
%% 比如对列表元素求和, 实际是通过对列表的头和尾进行递归求和实现的
sum(L) ->sum(L, 0).
sum([H|T], N) ->sum(T, H + N);
sum([], N) ->N.
3.4 fun、匿名函数、lambda
Erlang使用 fun 定义匿名函数, 效果同python、lisp中的lambda。
1> Hypot = fun(X, Y) -> math:sqrt(X*X + Y*Y) end.
#Fun<erl_eval.12.82930912>
2> Hypot(3, 4).
5.0
3> Double = fun(X) -> X*2 end.
#Fun<erl_eval.6.82930912>
4> Double(4).
8
3.4.1 以fun为参数的函数
1> L = [1, 2, 3, 4, 5, 6, 7, 8].
[1,2,3,4,5,6,7,8]
2> Even = fun(X) -> (X rem 2) =:= 0 end.
#Fun<erl_eval.6.82930912>
3> lists:map(Even, L).
[false,true,false,true,false,true,false,true]
4> lists:filter(Even, L).
[2,4,6,8]
3.4.2 返回fun的函数
1> Fruit = [apple, pear, orange].
[apple,pear,orange]
# 这里返回了一个fun函数, 它可以判断某个元素是否在指定的列表中
2> MakeTest = fun(L) -> (fun(X) -> lists:member(X, L) end) end.
#Fun<erl_eval.6.82930912>
3> IsFruit = MakeTest(Fruit).
#Fun<erl_eval.6.82930912>
4> IsFruit(pear).
true
3.4.3 自定义抽象流程控制
%% 自定义for循环
%% 从 I 循环到 Max
%% 首先匹配 for(Max, Max, F)
%% 前两个参数不相同将匹配 for(I, Max, F)
%% 然后将F(I) 与后续元素的计算结果拼接成列表
%% 当 I 循环到与 Max 相等时匹配for(Max, Max, F)
%% 如果两个字句互换下顺序, for(I, Max, F)匹配任意数字将导致死循环
for(Max, Max, F) ->[F(Max)];
for(I, Max, F) ->[F(I) | for(I+1, Max, F)].
3.5 简单的列表处理
sum 和 map的实现
-module(mylists).
-export([sum/1]).
-export([map/2]).
sum([H|T]) ->H + sum(T);
sum([]) ->0.
map(_, []) ->[];
map(F, [H|T]) ->[F(H) | map(F, T)].
使用sum和map改进total函数
-module(shop2).
-export([total/1]).
-import(mylists, [map/2, sum/1]).
total(L) ->
sum(map(fun({What, N}) ->shop:cost(What) * N end, L)).
3.6 列表解析
一般形式为 [F(X) || X <- L]。
表示:由F(X)组成列表, 其中X取值于列表L。
map更为简洁的实现
map(F, L) ->[F(X) || X <- L].
3.6.1 快速排序
%% 快速排序
%% 首先获取列表头元素
%% 然后使用列表解析将原列表的尾分成两个列表, 其中一个列表为比头元素小的集合, 另一个为比头元素大的集合
%% 最后递归排序子列表
qsort([]) ->[];
qsort([Pivot | T]) ->
qsort([X || X <- T, X < Pivot])
++ [Pivot] ++
qsort([X || X <- T, X >= Pivot]).
示例:
1> L = [23, 6, 2, 9, 27, 400, 78].
[23,6,2,9,27,400,78]
2> lib_misc:qsort(L).
[2,6,9,23,27,78,400]
3.6.2 毕达哥拉斯三元组
%% 毕达哥拉斯三元组
%% 获取在1到N之间的所有满足 A^2 + B^2 = C^2的整数集合{A, B, C}
%% 根据要求罗列出条件由Erlang自动完成匹配
pythag(N) ->
[ {A, B, C} ||
A <- lists:seq(1, N),
B <- lists:seq(1, N),
C <- lists:seq(1, N),
A+B+C =< N,
A*A+B*B =:= C*C
].
示例:
1> lib_misc:pythag(16).
[{3,4,5},{4,3,5}]
3.6.3 变位词
%% 变位词
%% 依次取列表中的某个元素(H <- L)
%% 对剩余的元素列表递归调用perms (perms(L--[H]))
%% 最后将两者合并为同一个列表([H|T])
perms([]) ->[[]];
perms(L) ->[[H|T] || H <- L, T <- perms(L--[H])].
示例:
1> lib_misc:perms("1234").
["1234","1243","1324","1342","1423","1432","2134","2143",
"2314","2341","2413","2431","3124","3142","3214","3241",
"3412","3421","4123","4132","4213","4231","4312","4321"]
3.7 算术表达式
操作 | 描述 |
---|---|
+X | +X |
-X | -X |
X*Y | X*Y |
X/Y | X/Y |
bnot X | 按位取反 |
X div Y | 整除取商 |
X rem Y | 整除取余 |
X band Y | 按位取与 |
X+Y | X+Y |
X-Y | X-Y |
X bor Y | 按位取或 |
X bxor Y | 按位异或 |
X bsl N | 按位左移 |
X bsr N | 按位右移 |
3.8 断言
断言以”when”开头, 可以在任何允许使用表达式的地方使用断言。
3.8.1 断言序列
使用分号分隔的断言集合(G1;G2;…;GN)相当于 OR
使用逗号分隔的断言集合(G1,G2,…GN)相当于 AND
断言是模式匹配的一种扩展, 因此需要断言表达式无副作用。
断言谓词
谓词 | 含义 |
---|---|
is_atom(X) | 是否为原子 |
is_binary(X) | 是否为二进制数据 |
is_constant(X) | 是否为常数 |
is_float(X) | 是否为浮点数 |
is_integer(X) | 是否为整数 |
is_number(X) | 是否为整数或浮点数 |
is_list(X) | 是否为列表 |
is_tuple(X) | 是否为元组 |
is_reference(X) | 是否为引用 |
is_pid(X) | 是否为进程标示符 |
is_port(X) | 是否为端口 |
is_record(X, Tag) | 是否为标记为Tag的记录 |
is_record(X, Tag, N) | 是否为标记为Tag大小为N的记录 |
is_function(X) | 是否为函数 |
is_function(X, N) | 是否为有N个参数的函数 |
断言BIF(内置函数)
函数 | 含义 |
---|---|
abs(X) | 取绝对值 |
element(N, X) | 取元组的第N个元素 |
float(X) | 转换为浮点数 |
hd(X) | 取列表的头部 |
length(X) | 取列表的长度 |
node() | 当前节点 |
node(X) | 创建节点(pid, port, reference) |
round(X) | 四舍五入取整 |
self() | 当前进程的标示符 |
size(X) | 取元组或二进制数据的大小 |
trunc(X) | 截取取整 |
tl(X) | 取列表尾部 |
3.8.2 断言样例
max(X, Y) when is_integer(X), is_integer(Y), X > Y ->X;
max(X, Y) ->Y.
3.8.3 true断言的使用
用在if表达式的末尾用于捕获其它所有的情况。
3.8.4 过时的断言函数
新版的Erlang测试函数的名称大都是is_fun()的形式。
3.9 记录
record用于使用一个名称来对应元组中的元素。
record的定义存放在以.hrl为后缀名的文件中。
%% 格式为 -record(Name, {key=value, ...}).
-record(todo, {status=reminder, who=joe, text}).
在shell中读取记录
1> rr("records.hrl").
[todo]
3.9.1 创建和更新记录
# 创建todo类型的新记录X, 值取默认
2> X=#todo{}.
#todo{status = reminder,who = joe,text = undefined}
# 创建todo类型的新记录X1, 自定义值
3> X1=#todo{status=urgent, text="Fix errata in book"}.
#todo{status = urgent,who = joe,text = "Fix errata in book"}
# 复制记录, 并修改其中一个key对应的值
4> X2=X1#todo{status=done}.
#todo{status = done,who = joe,text = "Fix errata in book"
3.9.2 从记录中提取字段值
# 采用模式匹配的方法提取字段值
5> #todo{who=W, text=Txt} = X2.
#todo{status = done,who = joe,text = "Fix errata in book"}
6> W.
joe
7> Txt.
"Fix errata in book"
# 或者使用"点语法"提取某个字段值
8> X2#todo.text.
"Fix errata in book"
3.9.3 在函数中对记录进行模式匹配
使用断言谓词is_record(X, todo)来匹配X是否为todo类型的记录。
3.9.4 记录只是元组的伪装
9> X2.
#todo{status = done,who = joe,text = "Fix errata in book"}
# 释放对记录的定义
10> rf(todo).
ok
# 将得到原有记录中每个key对应的值所组成的元组
11> X2.
{todo,done,joe,"Fix errata in book"}
3.10 case/if表达式
case/if表达式是对模式匹配的补充。
3.10.1 case表达式
# 首先对Expression求值
# 依次对Pattern进行模式匹配
# 对匹配的分支, 将执行其相应的表达式
case Expression of
Pattern1 [when Guard1] -> Expr_seq1;
Pattern2 [when Guard2] -> Expr_seq2;
...
end.
实例–模式匹配方式
%% filter的实现
%% filter函数依次对列表中的元素使用P函数求值
%% 根据P函数的结果(true或false)匹配filter1函数继续执行
%% 在filter1中则继续调用filter递归执行
filter(P, [H|T]) ->filter1(P(H), H, P, T);
filter(P, []) ->[].
filter1(true, H, P, T) ->[H|filter(P, T)];
filter1(false, H, P, T) ->filter(P, T).
实例-case方式
%% 对P(H)求值, 不同的结果执行不同的表达式
filter(P, [H|T]) ->
case P(H) of
true ->[H|filter(P, T)];
false ->filter(P, T)
end;
filter(P, []) ->[].
3.10.2 if表达式
# 对Guard依次求值, 为true则执行后面的Expr_seq
# 如果都不匹配, 则最后应有一个原子true的断言, 以保证至少有一个Expr_seq被执行。
if
Guard1 -> Expr_seq1;
Guard2 -> Expr_seq2;
...
true -> Expr_seq
end.
3.11 以自然顺序创建列表
总是在列表头部添加元素
从输入列表的头部提取元素, 加在一个输出列表的头部, 得到一个与输入相反的列表
需要调整顺序则调用高度优化的lists:reverse/1。
避免使用低效的 List ++ [H] 方式来生成自然顺序的列表。
3.12 累加器
%% 将整数列表按奇偶分成两个列表
%% 遍历两次, 一次取除2余1, 一次取除2余0
odds_and_evens(L) ->
Odds = [X || X <- L, (X rem 2) =:= 1],
Evens = [X || X <- L, (X rem 2) =:= 0],
{Odds, Evens}.
%% 遍历一次的版本
%% 依次对列表元素做除2操作, 根据不同的结果分别将其累加到Odds或Evens
odds_and_evens_acc(L) ->
odds_and_evens_acc(L, [], []).
odds_and_evens_acc([H|T], Odds, Evens) ->
case (H rem 2) of
1 ->odds_and_evens_acc(T, [H|Odds], Evens);
0 ->odds_and_evens_acc(T, Odds, [H|Evens])
end;
odds_and_evens_acc([], Odds, Evens) ->{Odds, Evens}.