`
blovedot
  • 浏览: 13201 次
  • 性别: Icon_minigender_1
  • 来自: 南昌
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

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也类似。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics