erlang 顺序编程
- 参考:erlang学习教程
安装
- sudo apt-get install erlang
进出shell
- erl
- ctrl+c,输入a 或者输入halt()
模块与函数
- demo:
%%模块名,名字要和文件名一样
-module(main).
%%设置函数为public,可以被外部模块访问,格式是[函数1/入参个数,函数2/入参格式 ...]
-export([double/1]).
%%函数实现,以点号结束
double(X) ->
2 * X.
- 编译:c(main).
- 执行:main:double(10).
- 重载demo:
-module(tut1).
-export([fac/1]).
%% 阶乘函数
fac(1) ->
1;
fac(N) ->
N * fac(N-1).
- erlang没有for,while等循环语句,一般用递归的方式来实现
- 函数重载时重载函数间用分号隔开,最后一个函数用点号表示结束
- 变量首字母必须大写,变量只能赋值一次,‘=’的意思不像其他语言一样是赋值的意思,而是数学意义上的判断两边是否相等的意思。当然变量第一次赋值时“=”的作用可以理解为赋值,变量的作用域和C/C++类似
- 首字母为小写字母的量叫做原子类型,就是常量,作用就是起标签的作用,没有具体的值
- 函数的最后一个表达试的值就会作为函数的返回值
原子类型
- 所有原子类型以小写字母开头,原子类型就是名字而已,没有其他含义,没有值,起标签说明等作用。原子类型不能被赋初值,不同的名字就表示不同的意义
- demo,英寸和厘米之间的转换:
-module(tut2).
-export([convert/2]).
convert(M,inch) ->
M / 2.54; %%erlang 里的除法和C/C++等不一样,就是数学里的除法,两整数相除不会丢弃小数部分,
convert(N,centimeter) ->
N * 2.54.
- erlang 的除法 “/”就是数学意义上的除法,除数和被除数为integer时不会丢弃小数部分,要丢弃可以用
- 比较运算符
op Description
== 等于
/= 不等于
=< 小于或等于
< 小于
>= 大于或等于
> 大于
=:= 恒等于
=/= 恒不等于 - 数学运算符
op Description Argument type
+ 一元 + number
- 一元 - number
+ 加法 number
- 减法 number
* 乘法 number
/ 浮点除法 number
div 整数除法 integer
bnot 一元 not 位运算 integer
rem 整数求余 integer
band 与运算 integer
bor 或运算 integer
bxor 异或运算 integer
bsl 左移运算 integer
bsr 右移运算 integer
元组
- 将某些元素分成组用更易于理解的方式表示的机制,一个元组由花括号括起来。比如{inch,3}可以表示为3英寸
- 将上面的英寸和厘米的函数用元组改一下:
-module(tut3).
-export(convert/1).
convert(centimeter,X) ->
{inch,X / 2.54};
convert(inch,Y) ->
{centimeter,Y * 2.54}.
erlang 列表
- 用来表示数据列表,用中括号括起来,demo:
[YaoMing,{height,226},{weight,230},{eyecolor,brown}].
- 列表长度不定,存储类型任意,逗号分隔
- 使用 ‘|’符号可以查看部分列表,demo:
[First|TheRest] = [1,2,3,4,5]. %%此时First为1,TheRest为[2,3,4,5]
[E1,E2|E3] = [1,2,3,4,5,6].%%E1为1,E2为2,E3为[3,4,5,6]
- 一个获取列表长度的函数demo:
-module(tut4).
-export(listLength/1).
listLength([]) ->
0;
listLength([First|Rest] ) - >
1 + listLength(Rest).
- erlang没有字符串类型,字符串可以用unicode字符的列表表示,demo:
[97,98,99] . %%等价于“abc”
erlang映射
- 映射用于表示key和value的关联关系,用“#{”和“}”括起来,比如 #{“key” => 42}.
- 计算alpha混合(让3D物体产生透明感的技术)的demo:
-module(color).
-export([new/4, blend/2]).
%%宏,定义函数is_channel(),检查值的类型和范围
-define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
%%创建新的映射,将键值与value值关联起来,新建映射的时候只能用“=>”符号
new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
?is_channel(B), ?is_channel(A) ->
#{red => R, green => G, blue => B, alpha => A}.
blend(Src,Dst) ->
blend(Src,Dst,alpha(Src,Dst)).
%%Dst映射会被更新为一个新的通道值,更新已经存在的映射键值对可以用“:=”操作符
blend(Src,Dst,Alpha) when Alpha > 0.0 ->
Dst#{
red := red(Src,Dst) / Alpha,
green := green(Src,Dst) / Alpha,
blue := blue(Src,Dst) / Alpha,
alpha := Alpha
};
%% _是占位符
blend(_,Dst,_) ->
Dst#{
red := 0.0,
green := 0.0,
blue := 0.0,
alpha := 0.0
}.
%%“:=”操作符取得键alpha关联的value值作为参数的值,映射中的其他键被忽略
alpha(#{alpha := SA}, #{alpha := DA}) ->
SA + DA*(1.0 - SA).
red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
SV*SA + DV*DA*(1.0 - SA).
green(#{green := SV, alpha := SA}, #{green := DV, alpha := DA}) ->
SV*SA + DV*DA*(1.0 - SA).
blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) ->
SV*SA + DV*DA*(1.0 - SA).
erlang的标准输出库函数,io:format
31> io:format("hello world~n", []).
hello world
ok
32> io:format("this outputs one Erlang term: ~w~n", [hello]).
this outputs one Erlang term: hello
ok
33> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).
this outputs two Erlang terms: helloworld
ok
34> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).
this outputs two Erlang terms: hello world
ok
- w 是占位符,n是换行符
- 一个华氏度转摄氏度的demo:
%% This module is in file tut5.erl
-module(tut5).
-export([format_temps/1]).
%% Only this function is exported
format_temps([])-> % No output for an empty list
ok;
format_temps([City | Rest]) ->
print_temp(convert_to_celsius(City)),
format_temps(Rest).
convert_to_celsius({Name, {c, Temp}}) -> % No conversion needed
{Name, {c, Temp}};
convert_to_celsius({Name, {f, Temp}}) -> % Do the conversion
{Name, {c, (Temp - 32) * 5 / 9}}.
print_temp({Name, {c, Temp}}) ->
io:format("~-15w ~w c~n", [Name, Temp]).
35> c(tut5).
{ok,tut5}
36> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
moscow -10 c
cape_town 21.11111111111111 c
stockholm -4 c
paris -2.2222222222222223 c
london 2.2222222222222223 c
ok
变量作用域,函数调用顺序等问题
- 一个计算列表最大值的demo:
-module(tut6).
-export([list_max/1]).
list_max([Head|Rest]) ->
list_max(Rest, Head).
list_max([], Res) ->
Res;
list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
list_max(Rest, Head);
list_max([Head|Rest], Result_so_far) ->
list_max(Rest, Result_so_far).
37> c(tut6).
{ok,tut6}
38> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).
7
- 函数重载,相同入参,同名函数的调用按顺序。比如上面的list_max.
- 变量的作用域和C类似,每个变量在其作用域内只能被赋值一次,不同的作用域,即使名字相同的变量也当做完全不同的变量。
- erlang中一个函数的返回值是函数最后一个表达式的值
- 重复赋值会出现错误,一个demo:
39> M = 5.
5
40> M = 6.
** exception error: no match of right hand side value 6
41> M = M + 1.
** exception error: no match of right hand side value 6
42> N = M + 1.
6
erlang的if和case
- 一个if的demo:
-module(tut9).
-export([test_if/2]).
test_if(A, B) ->
%%类似于C/C++中的switch-case
if
A == 5 ->
io:format("A == 5~n", []),
a_equals_5;
B == 6 ->
io:format("B == 6~n", []),
b_equals_6;
A == 2, B == 3 -> %That is A equals 2 and B equals 3
io:format("A == 2, B == 3~n", []),
a_equals_2_b_equals_3;
A == 1 ; B == 7 -> %That is A equals 1 or B equals 7
io:format("A == 1 ; B == 7~n", []),
a_equals_1_or_b_equals_7
end.
60> c(tut9).
{ok,tut9}
61> tut9:test_if(5,33).
A == 5
a_equals_5
62> tut9:test_if(33,6).
B == 6
b_equals_6
63> tut9:test_if(2, 3).
A == 2, B == 3
a_equals_2_b_equals_3
64> tut9:test_if(1, 33).
A == 1 ; B == 7
a_equals_1_or_b_equals_7
65> tut9:test_if(33, 7).
A == 1 ; B == 7
a_equals_1_or_b_equals_7
66> tut9:test_if(33, 33).
** exception error: no true branch found when evaluating an if expression
in function tut9:test_if/2 (tut9.erl, line 5)
- if和end配对使用
- 最后一个case结束时没有点号,中间用分号隔开
- 当不满足所有条件时会产生运行时错误
- 一个case的demo,之前有个英寸和米的转换函数,现在用case实现:
convert_length({centimeter, X}) ->
{inch, X / 2.54};
convert_length({inch, Y}) ->
{centimeter, Y * 2.54}.
-module(tut10).
-export([convert_length/1]).
convert_length(Length) ->
case Length of
{centimeter, X} ->
{inch, X / 2.54};
{inch, Y} ->
{centimeter, Y * 2.54}
end.
-case也和end配对,最后一个没有点号,中间用分号隔开
- case和if都有返回值
-一个计算某年某月天数的demo:
-module(tut11).
-export([month_length/2]).
month_length(Year, Month) ->
%% 被 400 整除的为闰年。
%% 被 100 整除但不能被 400 整除的不是闰年。
%% 被 4 整除但不能被 100 整除的为闰年。
Leap = if
trunc(Year / 400) * 400 == Year ->
leap;
trunc(Year / 100) * 100 == Year ->
not_leap;
trunc(Year / 4) * 4 == Year ->
leap;
true ->
not_leap
end,
case Month of
sep -> 30;
apr -> 30;
jun -> 30;
nov -> 30;
feb when Leap == leap -> 29;
feb -> 28;
jan -> 31;
mar -> 31;
may -> 31;
jul -> 31;
aug -> 31;
oct -> 31;
dec -> 31
end
70> c(tut11).
{ok,tut11}
71> tut11:month_length(2004, feb).
29
72> tut11:month_length(2003, feb).
28
73> tut11:month_length(1947, aug).
31
erlang并发编程
erlang进程
- erlang中,每个执行的线程都叫做一个process(和os的process的概念不一样)
- 并发输出hello和goodbye的一个demo:
-module(tut14).
-export([start/0, say_something/2]).
say_something(What, 0) ->
done;
say_something(What, Times) ->
io:format("~p~n", [What]),
say_something(What, Times - 1).
start() ->
%%spawn是申请进程的函数,返回值为PID,入参分别表示模块名,进程入口函数和其入参
spawn(tut14, say_something, [hello, 3]),
spawn(tut14, say_something, [goodbye, 3]).
5> c(tut14).
{ok,tut14}
6> tut14:start().
hello
goodbye
<0.63.0>
hello
goodbye
hello
goodbye
- <0.63.0>是start函数最后一个表达式spawn(tut14, say_something, [goodbye, 3]).的返回值,返回的是进程的标识符,也就是pid
erlang消息传递
-module(tut15).
-export([start/0, ping/2, pong/0]).
%%递归函数的出口函数,当第一个入参为0时给pong进程发送finished常量
ping(0, Pong_PID) ->
%%意思是给pong进程发送一个常量finished,然后输出打印
Pong_PID ! finished,
io:format("ping finished~n", []);
%%ping进程的入口函数,给pong进程发送ping元组,等待pong进程回复,然后递归的发消息
ping(N, Pong_PID) ->
Pong_PID ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1, Pong_PID).
%%pong进程的入口函数,等待接收ping进程发过来的finish常量或者ping元组
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
%%入口函数,起pong和ping两个进程
start() ->
Pong_PID = spawn(tut15, pong, []),
spawn(tut15, ping, [3, Pong_PID]).
1> c(tut15).
{ok,tut15}
2> tut15: start().
<0.36.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
ping finished
Pong finished
- <0.36,0>是start函数最后一个表达式的返回值
- end前的最后一个action是没有分号的
- 每个进程都有自己独立的队列来接收消息,当进程执行receive时,队列中的第一个消息和其选项匹配
erlang注册进程名称
- erlang提供了为每一个进程提供一个名称绑定的机制,这样进程间通信就可以通过进程名来实现,而不需要知道进程的进程标识符了。注册函数是register:
register(some_atom,Pid)
- 对上面进程间通信的demo代码的改写:
-module(tut16).
-export([start/0, ping/1, pong/0]).
ping(0) ->
pong ! finished,
io:format("ping finished~n", []);
ping(N) ->
pong ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1).
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
start() ->
%%注册新进程,进程名pong,入口函数pong()
register(pong, spawn(tut16, pong, [])),
spawn(tut16, ping, [3]).
2> c(tut16).
{ok, tut16}
3> tut16:start().
<0.38.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
ping finished
Pong finished
erlang健壮性
erlang超时处理
- 还是上面ping,pong的例子,在pong的处理函数中加入一个超时退出的time-out机制:
-module(tut19).
-export([start_ping/1, start_pong/0, ping/2, pong/0]).
ping(0, Pong_Node) ->
io:format("ping finished~n", []);
ping(N, Pong_Node) ->
{pong, Pong_Node} ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1, Pong_Node).
pong() ->
receive
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
%%单位是ms
after 5000 ->
io:format("Pong timed out~n", [])
end.
start_pong() ->
register(pong, spawn(tut19, pong, [])).
start_ping(Pong_Node) ->
spawn(tut19, ping, [3, Pong_Node]).
- after必须是receive中的最后一个,执行receive时,超时定时器就会启动,一旦收到正常的消息,就会停止定时器,否则5000ms后会执行超时操作
erlang错误处理
- 程序正常终止:进程执行exit(normal)或者运行完所有的代码而结束。
- 异常终止:程序因为触发运行时错误而异常终止。
- 一个进程可以调用link(other_pid)来和其他进程建立双向连接,当进程结束时,它会发送信号给所有和自己link的进程,当其他进程收到正常结束的信号时会忽略,收到异常终止信号时,会执行:
– 接收到异常终止信号的进程会忽略消息队列中的所有消息
– 杀死自己
– 将相同的错误信息传给连接到它的所有进程 - 所以可以使用连接的方式来将同一个事务的所有进程相连,当出现异常时所有进程都会被kill。
记录与宏
erlang程序分布的一个demo:
- mess_config.hrl
配置所需数据头文件
- mess_interface.hrl
客户端与 messager 之间的接口定义
- user_interface.erl
用户接口函数
- mess_client.erl
messager 系统客户端的函数
- mess_server.erl
messager 服务端的函数
- .hrl文件是erlang的头文件
- 一个头文件的demo:
%%%----FILE mess_interface.hrl----
%%%Message interface between client and server and client shell for
%%% messenger program
%%%Messages from Client to server received in server/1 function.
-record(logon,{client_pid, username}).
-record(message,{client_pid, to_name, message}).
%%% {'EXIT', ClientPid, Reason} (client terminated or unreachable.
%%% Messages from Server to Client, received in await_result/0 function
-record(abort_client,{message}).
%%% Messages are: user_exists_at_other_node,
%%% you_are_not_logged_on
-record(server_reply,{message}).
%%% Messages are: logged_on
%%% receiver_not_found
%%% sent (Message has been sent (no guarantee)
%%% Messages from Server to Client received in client/1 function
-record(message_from,{from_name, message}).
%%% Messages from shell to Client received in client/1 function
%%% spawn(mess_client, client, [server_node(), Name])
-record(message_to,{to_name, message}).
%%% logoff
%%%----END FILE----
- record格式:-record(name_of_record,{field_name1, field_name2, field_name3, ……}).
- record类似 C里面的struct,用-record声明,第一个参数是record的名字,第二个参数是一个元组,包含record的field
- record通过使用#来创建,比如 Opts1 = #logon{client_pid=1,username=”wuxiao”},如果我想访问username,用Opts1#username
- record的常用使用场景:
– 用来保存状态
– 用来保存配置选项
-在源文件中可以使用 -include(“head file name”). 来调用头文件
erlang 宏
- 格式:-define(server_node, messenger@super).
- a demo:
%%宏,定义函数is_channel(),检查值的类型和范围
-define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).