第三章 顺序型编程
Table of Contents
第三章顺序型编程
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}.