第4章 模块与函数
4.1 模块是存放代码的地方
① 模块是Erlang的基本代码单元,模块保存在.erl文件里,必须先编译才能运行,编译后的模块以.beam作为扩展名
② 函数的模式匹配
-module(geometry).
-export([area/1]).
area({rectangle, Width, Height}) -> Width * Height;
area({square, Side}) -> Side * Side.
% area函数有两个字句,字句以分号隔开,最后的字句以句号加空白结束。
4.1.1 常见错误
① c(geometry). 等只能在shell工作,不能放入模块
② 碰巧使用了与系统模块冲突的模块名,在编译时会报不能加载:stick directory (固定目录) 的错误
4.1.2 目录与代码路径
- pwd() : 打印当前目录
- ls() :列出当前目录所有文件名
- cd(Dir) : 修改当前目录为Dir
4.1.3 给代码添加测试
-module(geometry).
-export([test/0, area/1]).
test() ->
12 = area({rectangle, 3, 4}),
% 当函数返回12时,与12匹配,不会报错,test()函数返回test_worked
test_worked.
area({rectangle, Width, Height}) -> Width * Height.
4.1.5 分号放哪里
- 逗号(,)分隔函数调用,数据构造,模式中的参数
- 分号(;)分隔字句,如函数定义,case,if等表达式
- 句号(.)分隔函数整体与shell里的表达式
4.2 继续购物
编写程序计算购物所花的费用
商店 shop 模块:
-module(shop).
-export([cost/1]).
cost(oranges) -> 5;
cost(apple) -> 10.
假设购物列表:
buylist = [{orange, 4}, {apple, 2}] % 4、2 表示分别购买的数量
计算费用模块:
-module(counter).
-export([total/1]).
total([{What, N} | T]) -> shop:cost(What) * N + total(T);
total([]) -> 0.
4.3 基本的抽象单元
① 操作其他函数的函数被称为高阶函数,Erlang 中函数数据类型为 fun
Double = fun(X) -> 2 * X end.
> #Fun <erl_eval.6.5....>
Double(2)
> 4
4.3.1 以fun作为参数的函数
L = [1, 2, 3, 4]
lists:map(fun(X) -> 2*X end, L).
> [2, 4, 6, 8]
4.3.2 返回fun的函数
Fruit = [apple, pear].
MakeTest = fun(L) -> (fun(X) -> lists:member(X, L) end) end.
IsFruit = MakeTest(Fruit).
IsFruit(pear).
> true
4.3.3 定义你自己的控制抽象
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I) | for(I+1, Max, F)].
% 执行for(1, 10, fun(I) -> I end) 会得到 [1,2, ... 10]
4.4 简单的列表处理
-import(lists, [map/2, sum/1]) % 从lists模块导入map,sum函数,可直接使用map(Fun,...)来代替lists:map(Fun, ...)
4.5 列表推导
① 列表推导是一种快速创建列表的表达式
② 语法:[ F(X) || X <- L ] ,由F(X)组成的列表,X从列表L中提取。
[ X || Qualifier1, Qualifier2, ... ] ,限定符 Qualifier 可以是:
1)生成器,Pattern -> List
2)位串,BitStringPattern <= BitStringExpr
3)filter,可以是判断函数(返回true/false),或布尔表达式
如:[ X || {a, X} <- [ {a, 1}, {b, 2}, ... {a, 4}, hello ] ]. % 返回 [1, 4]
4.5.1 QuickSort
快速排序实现:
qsort([]) -> [];
qsort([Pivot | T]) ->
qsort([X || X <- T, X < Pivot ]) ++ [Pivot] ++
qsort([X || X <- T, X >= Pivot ]).
% ++ :中缀插入操作符
4.5.2 毕达哥拉斯三元数组
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 =:= C*C ].
4.5.3 回文构词
perms([]) -> [ [] ];
perms(L) -> [ [H|T] || H <- L, T <- perms(L -- [H]) ].
% 以L="cats"为例,X -- Y 是列表移除符,从X里移除Y中的元素
% 第一次 H1 = C, T1 <- perms("ats") : H11 = a, T11 <- perms("ts") : ...
H12 = t, T12 <- perms("as") : ...
H13 = a, T13 <- perms("at") : ...
% 所以:c 开头:cats cast ctas ctsa csat csta,以此类推到c\a\t\s开头
4.7 关卡
max(X, Y) when X > Y -> X;
max(X, Y) -> Y.
% 在函数定义里的头部使用关卡(通过when引入)
4.7.2 关卡示例
f(X, Y) when is_integer(X), X > Y, Y < -6 -> ...
% 当X是一个整数,X大于Y并且Y小于-6时函数执行
X =:= dog ; X =:= cat
% X 是一个cat或是一个dog成立
4.7.3 true关卡的作用
if
Guard -> Expr1;
Guard -> Expr2;
...
true -> Expr
end.
4.8 case与if表达式
4.8.1 case表达式
case Expression of
Pattern1 [ when Guard1 ] -> Expr1;
Pattern2 [ when Guard2 ] -> Expr2;
...
end
4.8.2 if表达式
① 必须至少有1个关卡执行结果为true,否则会发生异常错误
② 设最后一个关卡是原子true,确保其他关卡失败时表达式最后会被执行。
4.10 归集器
求列表中的奇数与偶数,原始程序:
odds_and_evens(L) ->
Odds = [ X || X <- L, (X rem 2) =:= 1 ],
Evens = [ X || X <- L, (X rem 1) =:= 0 ],
{Odds, Evens}.
% 这个程序的问题:遍历了列表两次
% 通过 case 语句实现只遍历1次
odds_and_evens(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}.
% 现在程序只遍历1次,把奇偶分别添加到合适的列表中。这些列表称为归集器(accumulator),更节省空间。