第三章 順序型程式設計
阿新 • • 發佈:2020-08-20
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}.