Erlang入门

Erlang入门(一)
读erlang.org上面的Erlang Course四天教程
1.数字类型,需要注意两点
1)B#Val表示以B进制存储的数字Val,比如
ruby 代码
7> 2#101.
1. 5
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->二进制存储的101就是10进制的5了
2)$Char表示字符Char的ascii编码,比如$A表示65

2.比较难以翻译的概念——atom,可以理解成常量,它可以包含任何字符,以小写字母开头,如果不是以小写字母开头或者是字母之外的符号,需要用单引号包括起来,比如abc,'AB'

3.另一个概念——Tuple,有人翻译成元组,可以理解成定长数组,是Erlang的基础数据结构之一:
ruby 代码
1. 8> {1,2,3,4,5}.
2. {1,2,3,4,5}
3. 9> {a,b,c,1,2}.
4. {a,b,c,1,2}
5. 10> size({1,2,3,a,b,c}).
6. 6

内置函数size求长度,元组可以嵌套元组或者其他结构。下面所讲的列表也一样。

4.另外一个基础数据结构就是各个语言都有的list(列表),在[]内以,隔开,可以动态改变大小,
python 代码
1. [123, xyz]
2. [123, def, abc]
3. [{person, 'Joe', 'Armstrong'},
4. {person, 'Robert', 'Virding'},
5. {person, 'Mike', 'Williams'}
6. ]

可以使用内置函数length求列表大小。以""包含的ascii字母代表一个列表,里面的元素就是这些字母的ascii值,比如"abc"表示列表[97,98,99]。

5.通过这两个数据结构可以组合成各种复杂结构,与Lisp的cons、list演化出各种结构一样的奇妙。

6.Erlang中变量有两个特点:
1)变量必须以大写字母开头
2)变量只能绑定一次,或者以一般的说法就是只能赋值一次,其实Erlang并没有赋值这样的概念,=号也是用于验证匹配。

7.模式匹配——Pattern Matching,Erlang的模式匹配非常强大,看了buaawhl的《Erlang语法提要》的介绍,模式匹配的功能不仅仅在课程中介绍的数据结构的拆解,在程序的分派也扮演重要角色,或者说Erlang的控制的流转是通过模式匹配来实现的。具体功能参见链接,给出书中拆解列表的例子:
python 代码
1. [A,B|C] = [1,2,3,4,5,6,7]
2. Succeeds - binds A = 1, B = 2,
3. C = [3,4,5,6,7]
4.
5. [H|T] = [1,2,3,4]
6. Succeeds - binds H = 1, T = [2,3,4]
7.
8. [H|T] = [abc]
9. Succeeds - binds H = abc, T = []
10.
11. [H|T] = []
12. Fails
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->
下面会给出更多模式匹配的例子,给出一个模块用来计算列表等

8.Erlang中函数的定义必须在一个模块内(Module),并且模块和函数的名称都必须是atom,函数的参数可以是任何的Erlang类型或者数据结构,函数要被调用需要从模块中导出,函数调用的形式类似:
moduleName:funcName(Arg1,Arg2,...).
写我们的第一个Erlang程序,人见人爱的Hello World:
java 代码
1. -module(helloWorld).
2. -export([run/1]).
3. run(Name)->
4. io:format("Hello World ~w~n",[Name]).

存为helloWorld.erl,在Erlang Shell中执行:
java 代码

1. 2> c(helloWorld).
2. {ok,helloWorld}
3. 3> helloWorld:run(dennis).
4. Hello World dennis
5. ok

打印出来了,现在解释下程序构造,
java 代码
1. -module(helloWorld).

这一行声明了模块helloWorld,函数必须定义在模块内,并且模块名称必须与源文件名相同。
java 代码

1. -export([run/1]).

而这一行声明导出的函数,run/1指的是有一个参数的run函数,因为Erlang允许定义同名的有不同参数的多个函数,通过指定/1来说明要导出的是哪个函数。
接下来就是函数定义了:
java 代码

1. run(Name)->
2. io:format("Hello World ~w~n",[Name]).

大写开头的是变量Name,调用io模块的format方法输出,~w可以理解成占位符,将被实际Name取代,~n就是换行了。注意,函数定义完了要以句号.结束。然后执行c(helloWorld).编译源代码,执行:
java 代码
1. helloWorld:run(dennis);

9.内置的常用函数:
java 代码

1. date()
2. time()
3. length([1,2,3,4,5])
4. size({a,b,c})
5. atom_to_list(an_atom)
6. list_to_tuple([1,2,3,4])
7. integer_to_list(2234)
8. tuple_to_list({})
9. hd([1,2,3,4]) %输出1,也就是列表的head
10. tl([1,2,3,4]) %输出[2,3,4],也就是列表的tail

10.常见Shell命令:
1)h(). 用来打印最近的20条历史命令
2)b(). 查看所有绑定的变量
3) f(). 取消(遗忘)所有绑定的变量。
4) f(Val). 取消指定的绑定变量
5) e(n). 执行第n条历史命令
6) e(-1). 执行上一条shell命令

11.又一个不知道怎么翻译的概念——Guard。翻译成约束?呵呵。用于限制变量的类型和范围,比如:
java 代码

1. number(X) - X 是数字
2. integer(X) - X 是整数
3. float(X) - X 是浮点数
4. atom(X) - X 是一个atom
5. tuple(X) - X 是一个元组
6. list(X) - X 是一个列表
7.
8. length(X) == 3 - X 是一个长度为3的列表
9. size(X) == 2 - X 是一个长度为2的元组
10.
11. X > Y + Z - X >Y+Z
12. X == Y - X 与Y相等
13. X =:= Y - X 全等于Y
14. (比如: 1 == 1.0 成功
15. 1 =:= 1.0 失败)

为了方便比较,Erlang规定如下的比较顺序:
java 代码
1. number < atom < reference < port < pid < tuple < list


12.忘了介绍apply函数,这个函数对于熟悉javascript的人来说很亲切,javascript实现mixin就得靠它,它的调用方式如下:
apply(Mod, Func, Args),三个参数分别是模块、函数以及参数列表,比如调用我们的第一个Erlang程序:
java 代码
1. apply(helloWorld,run,[dennis]).

13.if和case语句,if语句的结构如下:
java 代码

1. if
2. Guard1 ->
3. Sequence1 ;
4. Guard2 ->
5. Sequence2 ;
6. ...
7. end

而case语句的结构如下:

java 代码

1. case Expr of
2. Pattern1 [when Guard1] -> Seq1;
3. Pattern2 [when Guard2] -> Seq2;
4.
5. PatternN [when GuardN] -> SeqN
6. end

if和case语句都有一个问题,就是当没有模式匹配或者Grard都是false的时候会导致error,这个问题case可以增加一个类似java中default的:
java 代码

1. case Fn of
2.
3. _ ->
4. true
5. end

通过_指代任意的Expr,返回true,而if可以这样:
java 代码

1. if
2.
3. true ->
4. true
5. end

一样的道理。case语句另一个需要注意的问题就是变量范围,每个case分支中定义的变量都将默认导出case语句,也就是在case语句结束后可以被引用,因此一个规则就是每个case分支定义的变量应该一致,不然算是非法的,编译器会给出警告,比如:
java 代码

1. f(X) ->
2. case g(X) of
3. true -> A = h(X), B = A + 7;
4. false -> B = 6
5. end,
6. h(A).

如果执行true分支,变量A和变量B都被定义,而如果执行的false分支,只有变量B被引用,可在case语句执行后,h(A)调用了变量A,这是不安全的,因为变量A完全可能没有被定义,编译器将给出警告
variable 'A' unsafe in 'case' (line 10)
14.给出一些稍微复杂的模型匹配例子,比如用于计算数字列表的和、平均值、长度、查找某元素是否在列表中,我们把这个模块定义为list:
java 代码

1. -module(list).
2. -export([average/1,sum/1,len/1,double/1,member/2]).
3. average(X)->sum(X)/len(X).
4. sum([H|T]) when number(H)->H+sum(T);
5. sum([])->0.
6. len([_|T])->1+len(T);
7. len([])->0.
8. double([H|T]) -> [2*H|double(T)];
9. double([]) -> [].
10. member(H, [H|_]) -> true;
11. member(H, [_|T]) -> member(H, T);
12. member(_, []) -> false.
13.

细细体会,利用递归来实现,比较有趣。_用于指代任意的变量,当我们只关注此处有变量,但并不关心变量的值的时候使用。用分号;来说明是同一个函数定义,只是不同的定义分支,通过模式匹配来决定调用哪个函数定义分支。
另一个例子,计算各种图形的面积,也是课程中给出的例子:
java 代码

1. -module(mathStuff).
2. -export([factorial/1,area/1]).
3. factorial(0)->1;
4. factorial(N) when N>0->N*factorial(N-1).
5. %计算正方形面积,参数元组的第一个匹配square
6. area({square, Side}) ->
7. Side * Side;
8. %计算圆的面积,匹配circle
9. area({circle, Radius}) ->
10. % almost :-)
11. 3 * Radius * Radius;
12. %计算三角形的面积,利用海伦公式,匹配triangle
13. area({triangle, A, B, C}) ->
14. S = (A + B + C)/2,
15. math:sqrt(S*(S-A)*(S-B)*(S-C));
16. %其他
17. area(Other) ->
18. {invalid_object, Other}.
执行一下看看:
java 代码

1. 1> c(mathStuff).
2. {ok,mathStuff}
3. 2> mathStuff:area({square,2}).
4. 4
5. 3> mathStuff:area({circle,2}).
6. 12
7. 4> mathStuff:area({triangle,2,3,4}).
8. 2.90474
9. 5> mathStuff:area({other,2,3,4}).
10. {invalid_object,{other,2,3,4}}
Erlang使用%开始单行注释。
Erlang中的process——进程是轻量级的,并且进程间无共享。查了很多资料,似乎没人说清楚轻量级进程算是什么概念,继续查找中。。。闲话不 提,进入并发编程的世界。本文算是学习笔记,也可以说是《Concurrent Programming in ERLANG》第五张的简略翻译。
1.进程的创建
进程是一种自包含的、分隔的计算单元,并与其他进程并发运行在系统中,在进程间并没有一个继承体系,当然,应用开发者可以设计这样一个继承体系。
进程的创建使用如下语法:
java 代码
1. Pid = spawn(Module, FunctionName, ArgumentList)
spawn接受三个参数:模块名,函数名以及参数列表,并返回一个代表创建的进程的标识符(Pid)。
如果在一个已知进程Pid1中执行:
java 代码
1. Pid2 = spawn(Mod, Func, Args)
那么,Pid2仅仅能被Pid1可见,Erlang系统的安全性就构建在限制进程扩展的基础上。
2.进程间通信
Erlang进程间的通信只能通过发送消息来实现,消息的发送使用!符号:
java 代码
1. Pid ! Message
其中Pid是接受消息的进程标记符,Message就是消息。接受方和消息可以是任何的有效的Erlang结构,只要他们的结果返回的是进程标记符和消息。
消息的接受是使用receive关键字,语法如下:
java 代码
1. receive
2. Message1 [when Guard1] ->
3. Actions1 ;
4. Message2 [when Guard2] ->
5. Actions2 ;
6.
7. end
每一个Erlang进程都有一个“邮箱”,所有发送到进程的消息都按照到达的顺序存储在“邮箱”里,上面所示的消息Message1,Message2, 当它们与“邮箱”里的消息匹配,并且约束(Guard)通过,那么相应的ActionN将执行,并且receive返回的是ActionN的最后一条执行 语句的结果。Erlang对“邮箱”里的消息匹配是有选择性的,只有匹配的消息将被触发相应的Action,而没有匹配的消息将仍然保留在“邮箱”里。这 一机制保证了没有消息会阻塞其他消息的到达。
消息到达的顺序并不决定消息的优先级,进程将轮流检查“邮箱”里的消息进行尝试匹配。消息的优先级别下文再讲。 如何接受特定进程的消息呢?答案很简单,将发送方(sender)也附送在消息当中,接收方通过模式匹配决定是否接受,比如:
java 代码
1. Pid ! {self(),abc}
给进程Pid发送消息{self(),abc},利用self过程得到发送方作为消息发送。然后接收方:
java 代码
1. receive
2. {Pid1,Msg} ->
3.
4. end
通过模式匹配决定只有Pid1进程发送的消息才接受。

3.一些例子
仅说明下书中计数的进程例子,我添加了简单注释:
java 代码

1. -module(counter).
2. -compile(export_all).
3. % start(),返回一个新进程,进程执行函数loop
4. start()->spawn(counter, loop,[0]).
5. % 调用此操作递增计数
6. increment(Counter)->
7. Counter!increament.
8. % 返回当前计数值
9. value(Counter)->
10. Counter!{self(),value},
11. receive
12. {Counter,Value}->
13. %返回给调用方
14. Value
15. end.
16. %停止计数
17. stop(Counter)->
18. Counter!{self(),stop}.
19. loop(Val)->
20. receive
21. %接受不同的消息,决定返回结果
22. increament->
23. loop(Val+1);
24. {From,value}->
25. From!{self(),Val},
26. loop(Val);
27. stop->
28. true;
29. %不是以上3种消息,就继续等待
30. Other->
31. loop(Val)
32. end.
33.
34.
35.
调用方式:
java 代码

1. 1> Counter1=counter:start().
2. <0.30.0>
3. 2> counter:value(Counter1).
4. 0
5. 3> counter:increment(Counter1).
6. increament
7. 4> counter:value(Counter1).
8. 1

基于进程的消息传递机制可以很容易地实现有限状态机(FSM),状态使用函数表示,而事件就是消息。具体不再展开
4.超时设置
Erlang中的receive语法可以添加一个额外选项:timeout,类似:
java 代码
1. receive
2. Message1 [when Guard1] ->
3. Actions1 ;
4. Message2 [when Guard2] ->
5. Actions2 ;
6.
7. after
8. TimeOutExpr ->
9. ActionsT
10. end
after之后的TimeOutExpr表达式返回一个整数time(毫秒级别),时间的精确程度依赖于Erlang在操作系统或者硬件的实现。如果在time毫秒内,没有一个消息被选中,超时设置将生效,也就是ActionT将执行。time有两个特殊值:
1)infinity(无穷大),infinity是一个atom,指定了超时设置将永远不会被执行。
2) 0,超时如果设定为0意味着超时设置将立刻执行,但是系统将首先尝试当前“邮箱”里的消息。

超时的常见几个应用,比如挂起当前进程多少毫秒:
java 代码

1. sleep(Time) ->
2. receive
3. after Time ->
4. true
5. end.
比如清空进程的“邮箱”,丢弃“邮箱”里的所有消息:
java 代码

1. flush_buffer() ->
2. receive
3. AnyMessage ->
4. flush_buffer()
5. after 0 ->
6. true
7. end.
将当前进程永远挂起:
java 代码

1. suspend() ->
2. receive
3. after
4. infinity ->
5. true
6. end.

<!--<br> <br> Code highlighting produced by Actipro CodeHighlighter (freeware)<br> http://www.CodeHighlighter.com/<br> <br> --> 超时也可以应用于实现定时器,比如下面这个例子,创建一个进程,这个进程将在设定时间后向自己发送消息:
java 代码

1. -module(timer).
2. -export([timeout/2,cancel/1,timer/3]).
3. timeout(Time, Alarm) ->
4. spawn(timer, timer, [self(),Time,Alarm]).
5. cancel(Timer) ->
6. Timer ! {self(),cancel}.
7. timer(Pid, Time, Alarm) ->
8. receive
9. {Pid,cancel} ->
10. true
11. after Time ->
12. Pid ! Alarm
13. end.
5、注册进程
为了给进程发送消息,我们需要知道进程的Pid,但是在某些情况下:在一个很大系统里面有很多的全局servers,或者为了安全考虑需要隐藏进程 Pid。为了达到可以发送消息给一个不知道Pid的进程的目的,我们提供了注册进程的办法,给进程们注册名字,这些名字必须是atom。
基本的调用形式:
java 代码
1. register(Name, Pid)
2. 将Name与进程Pid联系起来
3.
4. unregister(Name)
5. 取消Name与相应进程的对应关系。
6.
7. whereis(Name)
8. 返回Name所关联的进程的Pid,如果没有进程与之关联,就返回atom:undefined
9.
10. registered()
11. 返回当前注册的进程的名字列表
6.进程的优先级
设定进程的优先级可以使用BIFs:
process_flag(priority, Pri)

Pri可以是normal、low,默认都是normal
优先级高的进程将相对低的执行多一点。

7.进程组(process group)
所有的ERLANG进程都有一个Pid与一个他们共有的称为Group Leader相关联,当一个新的进程被创建的时候将被加入同一个进程组。最初的系统进程的Group Leader就是它自身,因此它也是所有被创建进程及子进程的Group Leader。这就意味着Erlang的进程被组织为一棵Tree,其中的根节点就是第一个被创建的进程。下面的BIFs被用于操纵进程组:
group_leader()
返回执行进程的Group Leader的Pid
group_leader(Leader, Pid)
设置进程Pid的Group Leader为进程的Leader

8.Erlang的进程模型很容易去构建Client-Server的模型,书中有一节专门讨论了这一点,着重强调了接口的设计以及抽象层次的隔离问题,不翻译了。
明天要回家一个星期了,好好休息下。今天找到别人翻译的Erlang编程手册,值的好好读一遍。
所谓分布式的Erlang应用是运行在一系列Erlang节点组成的网络之上。这样的系统的性质与单一节点上的Erlang系统并没有什么不同。分布式这是个“大词”,Erlang从语言原生角度支持分布式编程,相比于java简单不少。
一、分布式机制
下列的BIFs是用于分布式编程:
spawn(Node, Mod, Func, Args)
启动远程节点的一个进程

spawn_link(Node, Mod, Func, Args)
启动远程节点的一个进程并创建连接到该进程

monitor_node(Node, Flag)
如果Flag是true,这个函数将使调用(该函数)的进程可以监控节点Node。如果节点已经舍弃或者并不存在,调用的进程将收到一个{nodedown,Node}的消息。如果Flag是false,监控将被关闭

node()
返回我们自己的进程name

nodes()
返回其他已知的节点name列表

node(Item)
返回原来Item的节点名称,Item可以是Pid,引用(reference)或者端口(port)

disconnect_node(Nodename)
从节点Nodename断开。

节点是分布式Erlang的核心概念。在一个分布式Erlang应用中,术语(term)节点(node)意味着一个可以加入分布式 transactions的运行系统。通过一个称为net kernal的特殊进程,一个独立的Erlang系统可以成为一个分布式Erlang系统的一部分。当net kernal进程启动的时候,我们称系统是alive的。

与远程节点上的进程进行通信,与同一节点内的进程通信只有一点不同:
java 代码
1. {Name, Node} ! Mess.

显然,需要接收方增加一个参数Node用于指定接受进程所在的节点。节点的name一般是用@隔开的atom类型,比如pong@dennis,表示计算机名为dennis上的pong节点。通过执行:
java 代码
1. erl -sname pong

将在执行的计算机中创建一个节点pong。为了运行下面的例子,你可能需要两台计算机,如果只有一台,只要同时开两个Erlang系统并以不同的节点名称运行也可以。

二、一些例子。
这个例子完全来自上面提到的翻译的连接,关于分布式编程的章节。我增加了截图和说明。
首先是代码:
java 代码

1. -module(tut17).
2.
3. -export([start_ping/1, start_pong/0, ping/2, pong/0]).
4.
5. ping(0, Pong_Node) ->
6. {pong, Pong_Node} ! finished,
7. io:format("ping finished~n", []);
8.
9. ping(N, Pong_Node) ->
10. {pong, Pong_Node} ! {ping, self()},
11. receive
12. pong ->
13. io:format("Ping received pong~n", [])
14. end,
15. ping(N - 1, Pong_Node).
16.
17. pong() ->
18. receive
19. finished ->
20. io:format("Pong finished~n", []);
21. {ping, Ping_PID} ->
22. io:format("Pong received ping~n", []),
23. Ping_PID ! pong,
24. pong()
25. end.
26.
27. start_pong() ->
28. register(pong, spawn(tut17, pong, [])).
29.
30. start_ping(Pong_Node) ->
31. spawn(tut17, ping, [3, Pong_Node]).

代码是创建两个相互通信的进程,相互发送消息并通过io显示在屏幕上,本来是一个单一系统的例子,现在我们让两个进程运行在不同的两个节点上。注意 start_ping方法,创建的进程调用ping方法,ping方法有两个参数,一个是发送消息的次数,一个就是远程节点的name了,也就是我们将要 创建的进程pong的所在节点。start_pong创建一个调用函数pong的进程,并注册为名字pong(因此在ping方法中可以直接发送消息给 pong)。
我是在windows机器上测试,首先打开两个cmd窗口,并cd到Erlang的安装目录下的bin目录,比如C:\Program Files\erl5.5.3\bin,将上面的程序存为tut17.erl,并拷贝到同一个目录下。我们将创建两个节点,一个叫 ping@dennis,一个叫pong@dennis,其中dennis是我的机器名。见下图:

采用同样的命令

erl -sname ping

创建ping节点。然后在pong节点下执行start_pong():


OK,这样就在节点pong上启动了pong进程,然后在ping节点调用start_ping,传入参数就是pong@dennis
java 代码
1. tut17:start_ping(pong@dennis).

执行结果如下图:

同样在pong节点上也可以看到:


结果如我们预期的那样,不同节点上的两个进程相互通信如此简单。我们给模块tut17增加一个方法,用于启动远程进程,也就是调用spawn(Node,Module,Func,Args)方法:
java 代码

1. start(Ping_Node) ->
2. register(pong, spawn(tut17, pong, [])),
3. spawn(Ping_Node, tut17, ping, [3, node()]).

pong进程启动Ping_Node节点上的进程ping。具体结果不再给出。
去了趟福州,事情没搞定,托给同学帮忙处理了,回家休息了两天就来上班了。回家这几天最大的收获是第四次重读《深入Java虚拟机》,以前不大明了的章节豁然开朗,有种开窍的感觉,水到渠成,看来技术的学习还是急不来。
闲话不提,继续Erlang的学习,上次学习到分布式编程的章节,剩下三章分别是错误处理、构造健壮的系统和杂项,错误处理和构造健壮的系统今天一起读了,仅摘记下。
任何一门语言都有自己的错误处理机制,Erlang也不例外,语法错误编译器可以帮你指出,而逻辑错误和运行时错误就只有靠程序员利用Erlang提供的机制来妥善处理,放置程序的崩溃。
Erlang的机制有:
1)监控某个表达式的执行
2)监控其他进程的行为
3)捕捉未定义函数执行错误等

一、catch和throw语句
调用某个会产生错误的表达式会导致调用进程的非正常退出,比如错误的模式匹配(2=3),这种情况下可以用catch语句:
catch expression
试看一个例子,一个函数foo:
java 代码

1. foo(1) ->
2. hello;
3. foo(2) ->
4. throw({myerror, abc});
5. foo(3) ->
6. tuple_to_list(a);
7. foo(4) ->
8. exit({myExit, 222}).

当没有使用catch的时候,假设有一个标识符为Pid的进程调用函数foo(在一个模块中),那么:
foo(1) - 返回hello
foo(2) - 语句throw({myerror, abc})执行,因为我们没有在一个catch中调用foo(2),因此进程Pid将因为错误而终止。

foo(3) - tuple_to_list将一个元组转化为列表,因为a不是元组,因此进程Pid同样因为错误而终止

foo(4) - 因为没有使用catch,因此foo(4)调用了exit函数将使进程Pid终止,{myExit, 222} 参数用于说明退出的原因。

foo(5) - 进程Pid将因为foo(5)的调用而终止,因为没有和foo(5)匹配的函数foo/1。

让我们看看用catch之后是什么样:
java 代码

1. demo(X) ->
2. case catch foo(X) of
3. {myerror, Args} ->
4. {user_error, Args};
5. {'EXIT', What} ->
6. {caught_error, What};
7. Other ->
8. Other
9. end.
再看看结果,
demo(1) - 没有错误发生,因此catch语句将返回表达式结果hello
demo(2) - foo(2)抛出错误{myerror, abc},被catch返回,因此将返回{user_error,abc}

demo(3) - foo(3)执行失败,因为参数错误,因此catch返回{'EXIT',badarg'},最后返回{caught_error,badarg}

demo(4) - 返回{caught_error,{myexit,222}}
demo(5) - 返回{caught_error,function_clause}

使用catch和throw可以将可能产生错误的代码包装起来,throw可以用于尾递归的退出等等。Erlang是和scheme一样进行尾递归优化的,它们都没有显式的迭代结构(比如for循环)

二、进程的终止
在进程中调用exit的BIFs就可以显式地终止进程,exit(normal)表示正常终止,exit(Reason)通过Reason给出非正常终止的原因。进程的终止也完全有可能是因为运行时错误引起的。

三、连接的进程
进程之间的连接是双向的,也就是说进程A打开一个连接到B,也意味着有一个从B到A的连接。当进程终止的时候,有一个EXIT信号将发给所有与它连接的进程。信号的格式如下:
{'EXIT', Exiting_Process_Id, Reason}
Exiting_Process_Id 是指终止的进程标记符
Reason 是进程终止的原因。如果Reason是normal,接受这个信号的进程的默认行为是忽略这个信号。默认对Exit信号的处理可以被重写,以允许进程对Exit信号的接受做出不同的反应。
1.连接进程:
通过link(Pid),就可以在调用进程与进程Pid之间建立连接
2.取消连接
反之通过unlink(Pid)取消连接。
3.创立进程并连接:
通过spawn_link(Module, Function, ArgumentList)创建进程并连接,该方法返回新创建的进程Pid

通过进程的相互连接,许多的进程可以组织成一个网状结构,EXIT信号(非normal)从某个进程发出(该进程终止),所有与它相连的进程以及与这些进 程相连的其他进程,都将收到这个信号并终止,除非它们实现了自定义的EXIT信号处理方法。一个进程链状结构的例子:
java 代码

1. -module(normal).
2. -export([start/1, p1/1, test/1]).
3. start(N) ->
4. register(start, spawn_link(normal, p1, [N - 1])).
5. p1(0) ->
6. top1();
7. p1(N) ->
8. top(spawn_link(normal, p1, [N - 1]),N).
9. top(Next, N) ->
10. receive
11. X ->
12. Next ! X,
13. io:format("Process ~w received ~w~n", [N,X]),
14. top(Next,N)
15. end.
16. top1() ->
17. receive
18. stop ->
19. io:format("Last process now exiting ~n", []),
20. exit(finished);
21. X ->
22. io:format("Last process received ~w~n", [X]),
23. top1()
24. end.
25. test(Mess) ->
26. start ! Mess.

执行:
java 代码

1. > normal:start(3).
2. true
3. > normal:test(123).
4. Process 2 received 123
5. Process 1 received 123
6. Last process received 123
7.
8. > normal:test(stop).
9. Process 2 received stop
10. Process 1 received stop
11. Last process now exiting
12. stop

四、运行时失败
一个运行时错误将导致进程的非正常终止,伴随着非正常终止EXIT信号将发出给所有连接的进程,EXIT信号中有Reason并且Reason中包含一个atom类型用于说明错误的原因,常见的原因如下:

badmatch - 匹配失败,比如一个进程进行1=3的匹配,这个进程将终止,并发出{'EXIT', From, badmatch}信号给连接的进程

badarg - 顾名思义,参数错误,比如atom_to_list(123),数字不是atom,因此将发出{'EXIT', From, badarg}信号给连接进程

case_clause - 缺少分支匹配,比如

java 代码

1. M = 3,
2. case M of
3. 1 ->
4. yes;
5. 2 ->
6. no
7. end.

没有分支3,因此将发出{'EXIT', From, case_clause}给连接进程

if_clause - 同理,if语句缺少匹配分支

function_clause - 缺少匹配的函数,比如:
java 代码

1. foo(1) ->
2. yes;
3. foo(2) ->
4. no.

如果我们调用foo(3),因为没有匹配的函数,将发出{'EXIT', From, function_clause} 给连接的进程。

undef - 进程执行一个不存在的函数

badarith - 非法的算术运算,比如1+foo。

timeout_value - 非法的超时时间设置,必须是整数或者infinity

nocatch - 使用了throw,没有相应的catch去通讯。

五、修改默认的信号接收action
当进程接收到EXIT信号,你可以通过process_flag/2方法来修改默认的接收行为。执行process_flag(trap_exit, true)设置捕获EXIT信号为真来改变默认行为,也就是将EXIT信号作为一般的进程间通信的信号进行接受并处理;process_flag (trap_exit,false)将重新开启默认行为。
例子:
java 代码

1. -module(link_demo).
2. -export([start/0, demo/0, demonstrate_normal/0, demonstrate_exit/1,
3. demonstrate_error/0, demonstrate_message/1]).
4. start() ->
5. register(demo, spawn(link_demo, demo, [])).
6. demo() ->
7. process_flag(trap_exit, true),
8. demo1().
9. demo1() ->
10. receive
11. {'EXIT', From, normal} ->
12. io:format("Demo process received normal exit from ~w~n",[From]),
13. demo1();
14. {'EXIT', From, Reason} ->
15. io:format("Demo process received exit signal ~w from ~w~n",[Reason, From]),
16. demo1();
17. finished_demo ->
18. io:format("Demo finished ~n", []);
19. Other ->
20. io:format("Demo process message ~w~n", [Other]),
21. demo1()
22. end.
23. demonstrate_normal() ->
24. link(whereis(demo)).
25. demonstrate_exit(What) ->
26. link(whereis(demo)),
27. exit(What).
28. demonstrate_message(What) ->
29. demo ! What.
30. demonstrate_error() ->
31. link(whereis(demo)),
32. 1 = 2.
33.
创建的进程执行demo方法,demo方法中设置了trap_exit为true,因此,在receive中可以像对待一般的信息一样处理EXIT信号,这个程序是很简单了,测试看看:
java 代码

1. > link_demo:start().
2. true
3. > link_demo:demonstrate_normal().
4. true
5. Demo process received normal exit from <0.13.1>
6. > link_demo:demonstrate_exit(hello).
7. Demo process received exit signal hello from <0.14.1>
8. ** exited: hello **
9.
10. > link_demo:demonstrate_exit(normal).
11. Demo process received normal exit from <0.13.1>
12. ** exited: normal **
13.
14. > link_demo:demonstrate_error().
15. !!! Error in process <0.17.1> in function
16. !!! link_demo:demonstrate_error()
17. !!! reason badmatch
18. ** exited: badmatch **
19. Demo process received exit signal badmatch from <0.17.1>

六、未定义函数和未注册名字
1.当调用一个未定义的函数时,Mod:Func(Arg0,...,ArgN),这个调用将被转为:
error_handler:undefined_function(Mod, Func, [Arg0,...,ArgN])
其中的error_handler模块是系统自带的错误处理模块

2.当给一个未注册的进程名发送消息时,调用将被转为:
error_handler:unregistered_name(Name,Pid,Message)

3.如果不使用系统自带的error_handler,可以通过process_flag(error_handler, MyMod) 设置自己的错误处理模块。

七、Catch Vs. Trapping Exits
这两者的区别在于应用场景不同,Trapping Exits应用于当接收到其他进程发送的EXIT信号时,而catch仅用于表达式的执行。

第8章介绍了如何利用错误处理机制去构造一个健壮的系统,用了几个例子,我将8.2节的例子完整写了下,并添加客户端进程用于测试:
java 代码
1. -module(allocator).
2. -export([start/1,server/2,allocate/0,free/1,start_client/0,loop/0]).
3. start(Resources) ->
4. Pid = spawn(allocator, server, [Resources,[]]),
5. register(resource_alloc, Pid).
6. %函数接口
7. allocate() ->
8. request(alloc).
9. free(Resource) ->
10. request({free,Resource}).
11. request(Request) ->
12. resource_alloc ! {self(),Request},
13. receive
14. {resource_alloc, error} ->
15. exit(bad_allocation); % exit added here
16. {resource_alloc, Reply} ->
17. Reply
18. end.
19. % The server.
20. server(Free, Allocated) ->
21. process_flag(trap_exit, true),
22. receive
23. {From,alloc} ->
24. allocate(Free, Allocated, From);
25. {From,{free,R}} ->
26. free(Free, Allocated, From, R);
27. {'EXIT', From, _ } ->
28. check(Free, Allocated, From)
29. end.
30. allocate([R|Free], Allocated, From) ->
31. link(From),
32. io:format("连接客户端进程~w~n",[From]),
33. From ! {resource_alloc,{yes,R}},
34. server(Free, [{R,From}|Allocated]);
35. allocate([], Allocated, From) ->
36. From ! {resource_alloc,no},
37. server([], Allocated).
38. free(Free, Allocated, From, R) ->
39. case lists:member({R,From}, Allocated) of
40. true ->
41. From ! {resource_alloc,ok},
42. Allocated1 = lists:delete({R, From}, Allocated),
43. case lists:keysearch(From,2,Allocated1) of
44. false->
45. unlink(From),
46. io:format("从进程~w断开~n",[From]);
47. _->
48. true
49. end,
50. server([R|Free],Allocated1);
51. false ->
52. From ! {resource_alloc,error},
53. server(Free, Allocated)
54. end.
55.
56. check(Free, Allocated, From) ->
57. case lists:keysearch(From, 2, Allocated) of
58. false ->
59. server(Free, Allocated);
60. {value, {R, From}} ->
61. check([R|Free],
62. lists:delete({R, From}, Allocated), From)
63. end.
64. start_client()->
65. Pid2=spawn(allocator,loop,[]),
66. register(client, Pid2).
67. loop()->
68. receive
69. allocate->
70. allocate(),
71. loop();
72. {free,Resource}->
73. free(Resource),
74. loop();
75. stop->
76. true;
77. _->
78. loop()
79. end.
80.
回家了,有空再详细说明下这个例子吧。执行:
java 代码
1. 1> c(allocator).
2. {ok,allocator}
3. 2> allocator:start([1,2,3,4,5,6]).
4. true
5. 3> allocator:start_client().
6. true
7. 4> client!allocate
8. .
9. allocate连接客户端进程<0.37.0>
10.
11. 5> client!allocate.
12. allocate连接客户端进程<0.37.0>
13.
14. 6> client!allocate.
15. allocate连接客户端进程<0.37.0>
16.
17. 7> allocator:allocate().
18. 连接客户端进程<0.28.0>
19. {yes,4}
20. 8> client!{free,1}.
21. {free,1}
22. 9> client!{free,2}.
23. {free,2}
24. 10> client!allocate.
25. allocate连接客户端进程<0.37.0>
26.
27. 11> client!allocate.
28. allocate连接客户端进程<0.37.0>
29.
30. 12> client!stop.
31. stop
32. 13> allocator:allocate().
33. 连接客户端进程<0.28.0>
34. {yes,3}
35. 14> allocator:allocate().
36. 连接客户端进程<0.28.0>
37. {yes,2}
38. 15> allocator:allocate().
39. 连接客户端进程<0.28.0>
40. {yes,1}
暂时搞不到《Programming Erlang》,最近就一直在看Erlang自带的例子和Reference Manual。基础语法方面有一些过去遗漏或者没有注意的,断断续续仅记于此。

1。Erlang的保留字有:
after and andalso band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse query receive rem try when xor<!-- Empty -->
基本都是些用于逻辑运算、位运算以及特殊表达式的符号
2.Erlang的类型,除了在前面入门一提到的类型外,还包括:
1)Binary,用于表示某段未知类型的内存区域
比如:
1> <<10>>.
<<10>>
2> <<"ABC">>.
<<65>>

2)Reference,通过调用mk_ref/0产生的运行时的unique term

3)String,字符串,Erlang中的字符串用双引号包括起来,其实也是list。编译时期,两个邻近的字符串将被连接起来,比如"string" "42" 等价于 "string42"

4)Record,记录类型,与c语言中的struct类似,模块可以通过-record属性声明,比如:
-module(person).
-export([new/2]).
-record(person, {name, age}).
new(Name, Age) ->
#person{name=Name, age=Age}.
1> person:new(dennis, 44).
{person,dennis,44}
在编译后其实已经被转化为tuple。可以通过Name#person.name来访问Name Record的name属性。

3.模块的预定义属性:
-module(Module). 声明模块名称,必须与文件名相同
-export(Functions). 指定向外界导出的函数列表
-import(Module,Functions). 引入函数,引入的函数可以被当作本地定义的函数使用
-compile(Options). 设置编译选项,比如export_all
-vsn(Vsn). 模块版本,设置了此项,可以通过beam_lib:version/1 获取此项信息
可以通过-include和-include_lib来包含文件,两者的区别是include-lib不能通过绝对路径查找文件,而是在你当前Erlang的lib目录进行查找。

4.try表达式,try表达式可以与catch结合使用,比如:
try Expr
catch
throw:Term -> Term;
exit:Reason -> {'EXIT',Reason}
error:Reason -> {'EXIT',{Reason,erlang:get_stacktrace()}}
end

不仅如此,try还可以与after结合使用,类似java中的try..finally,用于进行清除作用,比如:
termize_file(Name) ->
{ok,F} = file:open(Name, [read,binary]),
try
{ok,Bin} = file:read(F, 1024*1024),
binary_to_term(Bin)
after
file:close(F)
end.


5.列表推断(List Comprehensions),函数式语言特性之一,Erlang中的语法类似:
[Expr || Qualifier1,...,QualifierN]
Expr可以是任意的表达式,而Qualifier是generator或者filter。还是各举例子说明下。
1> [X*2 || X <->
[2,4,6]

2> L=[1,2,3,4,5,6,7].
[1,2,3,4,5,6,7]
3> [X|X<-l>=3].
[3,4,5,6,7]
再看几个比较酷的例子,来自Programming Erlang,
比如快速排序:
-module(qsort).
-export([qsort/1]).
qsort([])->[];
qsort([Pivot|T])->
qsort([X||X<-t>
6.宏,定义常量或者函数等等,语法如下:

-define(Const, Replacement).

-define(Func(Var1,...,VarN), Replacement).

使用的时候在宏名前加个问号?,比如?Const,Replacement将插入宏出现的位置。系统预定义了一些宏:

?MODULE 表示当前模块名

?MODULE_STRING 同上,但是以字符串形式

?FILE 当前模块的文件名

?LINE 调用的当前代码行数

?MACHINE 机器名

Erlang的宏与C语言的宏很相似,同样有宏指示符,包括:

-undef(Macro).
取消宏定义

-ifdef(Macro).
当宏Macro有定义的时候,执行以下代码

-ifndef(Macro).
同上,反之
-else.
接在ifdef或者ifndef之后,表示不满足前者条件时执行以下代码

-endif.
if终止符
假设宏-define(Square(X),X*X).用于计算平方,那么??X将返回X表达式的字符串形式,类似C语言中#arg

一个简单的宏例子:

ruby 代码

1. -module(macros_demo).
2. -ifdef(debug).
3. -define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
4. -else.
5. -define(LOG(X), true).
6. -endif.
7. -define(Square(X),X*X).
8. -compile(export_all).
9. test()->
10. A=3,
11. ?LOG(A),
12. B=?Square(A),
13. io:format("square(~w) is ~w~n",[A,B]).
当编译时不开启debug选项的时候:

17> c(macros_demo).

{ok,macros_demo}

18> macros_demo:test().

square(3) is 9

当编译时开启debug之后:

19> c(macros_demo,{d,debug}).

{ok,macros_demo}

20> macros_demo:test().

{macros_demo,11}: 3

square(3) is 9

ok

可以看到LOG的输出了,行数、模块名以及参数

7、Process Dictionary,每个进程都有自己的process dictionary,用于存储这个进程内的全局变量,可以通过下列

BIFs操作:

put(Key, Value)

get(Key)

get()

get_keys(Value)

erase(Key)

erase()

8、关于分布式编程,需要补充的几点

1)节点之间的连接默认是transitive,也就是当节点A连接了节点B,节点B连接了节点C,那么节点A也与节点C互相连接

可以通过启动节点时指定参数-connect_all false来取消默认行为

2)隐藏节点,某些情况下,你希望连接一个节点而不去连接其他节点,你可以通过在节点启动时指定-hidden选项

来启动一个hidden node。在此情况下,通过nodes()查看所有连接的节点将不会出现隐藏的节点,想看到隐藏的节点

可以通过nodes(hidden)或者nodes(connected)来查看。

完整的erl选项如下:

-connect_all false 上面已经解释。
-hidden 启动一个hidden node
-name Name 启动一个系统成为节点,使用long name.
-setcookie Cookie 与Erlang:set_cookie(node(), Cookie).相同,设置magic cookie

-sname Name 启动一个Erlang系统作为节点,使用short name

注意,short name启动的节点是无法与long name节点通信的。

.一个小细节,在Erlang中小于等于是用=<表示,而不是一般语言中的<=语法,我犯过错误的地方,同样,不等于都是用/号,而不是
!,比如/=、=/=。

10.and or 和andalso orelse的区别

and和or会计算两边的表达式,而andalso和orelse的求值采用短路机制,比如exp1 andalso exp2,当exp1返回false之后,就不会去求值
exp2,而是直接返回false,而exp1 and exp2会对exp1和exp2都进行求值,or与orelse也类似。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值