《Erlang程序设计》第三章 顺序型编程

第三章 顺序型编程

第三章顺序型编程

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*YX*Y
X/YX/Y
bnot X按位取反
X div Y整除取商
X rem Y整除取余
X band Y按位取与
X+YX+Y
X-YX-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}.

Date: 2013-05-26 12:00:16 CST

Author: matrix

Org version 7.8.11 with Emacs version 24

Validate XHTML 1.0
 
 
 
 

转载于:https://www.cnblogs.com/scheme/archive/2013/05/26/3099830.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值