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)).
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值