我还是特别庆幸我掌握了Mathematica。回过头来看这半年写的程序,很大程度上已经不仅仅是纯的函数式编程了,由于模式匹配特别好使,所以使用了大量的规则代换。当一开始看到Prolog程序时倍感亲切,但后来越看越不对。我一开始认为规则代换编程(或者叫逻辑编程)是从函数式编程中衍生出来的,现在证实这完全是个错误。事实情况是两者在地位上没有从属关系,但却可以相互表示对方。
然而我更庆幸的是,我认真学过谓词演算。原因是高二信息奥赛有一道类似“A说B说谎B说C说谎C说我是无辜的D说A没说谎然后问谁说谎了”的题目没有做出来,心里留下了阴影,后来得知谓词演算就是用来解决这问题的,于是大二花整整一暑假仔细研读了一遍。
逻辑编程中不再有函数和操作对象的概念了,它组成的元素是“事实”和“规则”。其中“事实”是一个谓词短语,譬如Predicate1(arg1, arg2)表示某个动作被施加在arg1和arg2上。当然这种说法是相当不确切的,Predicate1可以是一个有实义的动作也可以不是,同样arg1和arg2可以是有实义的对象也可以不是。更一般地来说,谓词短语就是用一个叫做谓词的记号(字符串表示),将谓词作用域(圆括号内)中的若干(用逗号分隔)记号(同样字符串表示)建立起了一种抽象的关联。如果希望想的更形象一点,就可以认为arg1和arg2是图中的两个节点,一个谓词短语代表了节点之间的一个路径。需要注意的是,由于谓词短语的作用域是一个有序表,因此这个图显然是个有向图。
===============================================================
对于逻辑编程,谓词不一定需要产生动作,这是和其他语言中的函数最大的区别。因此它不需要定义类型,不需要和CPU指令相关联,而仅仅是被看做一个“事实”记录在知识库中。这一点Mathematica和它十分类似。比如
Predicate1(arg1, arg2).
就是一个事实。更确切地说,它告诉Prolog这是一个永真式,等价于
Predicate1(arg1, arg2) is always true.
“规则”在Prolog中有很多。第一个规则是蕴含。比如
Predicate1(arg1, arg2):- Predicate2(arg1),Predicate2(arg2)
不包含在括号中的逗号一律读作“且”。则这句话读作
“如果Predicate2(arg1)为真且Predicate2(arg2)为真,则Predicate1(arg1, arg2)为真”,或
“Predicate2(arg1)和Predicate2(arg2)均为真,蕴含Predicate1(arg1, arg2)为真”。
第二个规则是等价。如果已知Predicate1(arg1, arg2),那么输入
Predicate1(X, Y)
就会得到X=arg1, Y=arg2。
如果以Predicate1为谓词的事实还有很多,那么Prolog就会列出X,Y同时为真的所有组合。
此外还有模式匹配,和Mathematica的几乎一样,不废话了。Prolog的语言特性还没有看完,但是比起Haskell什么的要简单多了。Prolog的特点是,程序很难出错(就是执行不下去),最后也肯定能得到一个结果。但是如果不小心设计,那么这个结果通常不是你想要的那个。
=======================================================================
另外一个非常重要的内容就是规则的递归。规则的递归和函数的递归有很大差异。函数的递归最终可以写成一个高阶函数的嵌套,内层函数求值得到结果返回外层函数,外层函数求值将结果返回更外层,依此类推直到调用函数的最底层为止。函数递归要求存在一个递归终点,这个终点就是已知函数值的函数表达式。
而规则的递归,则是通过递归的蕴含式创造出同一个谓词的新的谓词短语,同时将新旧谓词作用域内的项建立等价关系。规则递归同样需要有一个递归终点,这个终点就是一个已知的事实。既然这个事实为真,则可将蕴含这个永真式的谓词短语(可以方便地理解为“外层调用它的函数”)作用域项依照等价关系进行代替,直到所有项的等价关系都已经建立,就能够确保这个谓词短语为真,那么这个谓词短语里面要求的那一项的结果肯定也是正确的。
举个例子,在prolog中同样有List数据结构,比如[1,2,3,4,5](和Python一样)。
首先要提到这么一种用法:[X|Y],如果[X|Y]=[1,2,3,4,5],那么X=1,Y=[2,3,4,5],和car/cdr是一个性质。[如果表中只有一个元素,那么X返回那个元素,而Y返回一个空表,这一点很重要。]
在Prolog上实现的列表追加是这样子的:
append(List_For_Appending, Original_List, New_List)
就是把头一个加到第二个上,然后得到第三个。
定义是这样的:
append([],List,List).
这是递归终点,也是一个事实,指的是往列表上加一个空列表还是它自己。需要注意的是,尽管prolog中没有数据类型,但是出现两个同名的项则意味着它们不论是什么,都指的是同一个东西。同名等价,好像叫做“自反律”吧。
下面这个比较折腾:
append([First|Rest],Original_List,[First|NewRest]):-append(Rest,Original_List,NewRest)
如果输入
append(List_For_Appending, Original_List, New_List),那么
则有
New_List
=[First(List_For_Appending)|Rest(New_List)]
=[First(Rest(List_For_Appending))|Rest(Rest(New_List))]
=[First(Rest(First(Rest(List_For_Appending))))|Rest(Rest(Rest(New_List)))]
直到当append的第一项是[]的时候,则不在继续生成新的蕴含式,而应用上面的规则,建立如下等价关系:
Rest(...(Rest(New_List))...)=Original_List
然后一步步逆推,得到
Rest(New_List)
最后得到New_List
=======================================================================
这是一个不算复杂的例子,更牛逼的例子其实大家都知道。我记得高中二年级看严蔚敏的数据结构(还是Pascal版)的时候,那个汉诺塔的递归程序怎么也看不明白,后来才明白,那个时候我无论如何也不会明白。用Prolog写出来的程序其实很像:
move(1,X,Y,_) :-
write('Move top disk from '),
write(X),
write(' to '),
write(Y),
nl.
move(N,X,Y,Z) :-
N>1,
M is N-1,
move(M,X,Z,Y),
move(1,X,Y,_),
move(M,Z,Y,X).
move谓词中作用域的项应该这么解释:
move(自顶向下数第n个盘子,当前所在的柱子,移动目标的柱子,途中经过的柱子)
对于三个盘子的情形,可以解释为
move(3,left,right,center)这个事实为真,只有在
2) move(2,left,center,right)为真,且
1) move(1,left,right,center)为真,且
3) move(2,center,right,left)为真的情况下
1)好说,只要几个字符串都输出完成就为真了。但是2)和3)没有可供匹配的事实,所以只能进一步展开蕴含式
2)
move(2,left,center,right)这个事实为真,只有在
move(1,left,right,center)为真,且
move(1,left,center,right)为真,且
move(1,right,center,left)为真的情况下
3)
move(2,center,right,left)这个事实为真,只有在
move(1,center,left,right)为真,且
move(1,center,right,left)为真,且
move(1,left,right,center)为真的情况下
于是,最终的结果,在屏幕上显示的就是
Move top disk from left to right
Move top disk from left to center
Move top disk from right to center
哥加的分割线------------------------------------
Move top disk from left to right
哥加的分割线------------------------------------
Move top disk from center to left
Move top disk from center to right
Move top disk from left to right
当时在书上用Pascal写出来的的牛逼之处在于它并没有使用函数(Function),而是过程(Procedure)。原因是它并不需要返回值,而只是来检测第一个参数是不是1就可以了。这就是谓词的力量。函数用来表示“在一个对象上施加某种操作,会得到另外某种特定的对象”。而表达相同意思的谓词则是用来表示“‘在一个对象上施加某种操作,会得到另外某种特定的对象’这个命题是真的”。
所以不妨这么理解,谓词短语是个隐函数。
或者说,函数式编程还可以分为“显式函数编程”和“隐式函数编程”?
======================
======================
今天开始看Prolog的Built-in,看到求列表的谓词length是这样写的:
length(List,len)
将List换成一个实际的列表,比如[1,2,3],那么得到的结果是
len=3
当时我大惊,以为逻辑编程竟然和过程式编程一样,需要将值储存在一个变量里。但后来想想还是概念有误。首先,length(X,Y)是个谓词短语而不是函数表达式,它的值只有真假。其次,3是个原子而并非一个事实或者断言,所以它没有真假。Prolog的工作方式不是直接将length同3相关联,而是同length作用域中的某一个对象相关联,推导出一个断言,能够满足length谓词短语的值为真。
这使我想起了当年看那个Pascal汉诺塔程序时,程序使用的是Procedure而非Function。原因在于,这个程序所看重的并不是函数返回的结果,而是这个函数的“边际效应”,也就是在屏幕上输出的结果。所谓“边际效应”指得就是函数中的一个额外的动作,它并没有改变当前函数参数的状态(但是却改变了某个对象的状态)。因此函数只进行了一个“边际效应”的动作而并没有产生结果。回顾一下《算法导论》中一开始就遇到的“递归树”,如果按照函数值传递,那么递归的过程就是不断拓展这个递归树,直到递归树中某个结点的值是已知的,然后在沿递归树向根节点的方向逐步更新每层函数的值,直到根节点的值也更新为止。形式看起来像这样:
RecursionFunction(arg1, arg2,...)=Blah(RecursionFunction(arg1, arg2,...))
但是此处并不是这样,由于结果并非函数值而是函数的一个参数,那么当拓展递归树的新节点时那个参数就也被带入到下一层的递归调用中了。所以当到达递归终点时,那个作为结果的参数就已经同递归终点的已知的值建立了显式的关系,也就是说结果已经知道了,所以不会再有回溯的过程了。它的形式看起来像这样:
RecursionFunction(arg1, arg2,...)=RecursionFunction(Blah(arg1, arg2,...))
这就是线性递归和尾递归的区别,可以看到关于这两个的讨论还是很多的。具体内容在这里讲得很详细,
http://topic.csdn.net/u/20071023/08/87c95c73-6022-4b39-8aae-b2fb38fb0c38.html
http://www.cnblogs.com/JeffreyZhao/archive/2009/03/26/tail-recursion-and-continuation.html
需要注意的一点是,尾递归是实参的传递,因此尾递归改变了状态,因此应用尾递归的函数是不具备引用透明性的,因此应用尾递归的函数不是函数式编程范畴内的函数。
【这话说的有问题,但是我还没想好更合适的说法】
【果然说的很有问题,引用透明性所指的“状态依赖”指的是依赖最底层函数(也就是调用入口)的状态,按照这个说法尾递归没有改变状态,仍然具有引用透明性(前提是“边际效应”的动作和参数无关),刚才特地查了SICP。这就意味着,除了像Prolog进行模式匹配可能还比较费劲以外,剩下的Lisp都做得到】
==========================================================
上一篇日志结束时,提到了Prolog像是隐函数式编程。现在要具体讨论一下隐函数。比如y=Function(x)是一个显式函数。如果写成y-Function(x)=0就是一个隐函数。我们现在关注的是其中的一个谓词,也就是那个等号。按照Prolog的写法(或者是通用的谓词演算的记法),这个隐函数就应该被写为
=(y, Function(x))
告诉Prolog这个断言为真,那么
?-=(y,Function(x)+1)
得到的结果就会是no。
需要注意的是,Prolog永远只能给出一个它认为值为true的等价关系,而无法给出位列等价关系中的任何一方。上面这个谓词短语将函数表达式和函数值联系起来,但是在Prolog中却无法独立存在。这是因为在Prolog中除了built-in的原子,剩下一切信息都是以谓词短语的形式存在的。换句话说,用户定义的信息必将是连接某些原子的一个通路【好像越说越不明白了】。这就意味着,在【显式】函数式编程中彼此分立的函数表达式和函数值,在Prolog中却被绑在了同一个谓词短语之中。
进一步地来说,为什么像Haskell这样的函数式编程函数表达式和函数值能彼此分立呢?这是因为Lambda演算中的替换是无条件的,它高于其他一切算符,所以才会有apply和eval这种替换和规约的规则。但是在Prolog中,等号只是体现等价关系的谓词而已,它表示两者存在可以相互替代的【关系】,但并不表示替代的这个【行为】的发生。可以说函数编程和逻辑编程的区别,就是Lambda函数的存在了。如果替换的行为不会发生,便意味着Prolog中没有显式函数的概念。这样Prolog完全不具备引用透明性,而只能依赖由谓词短语的真值来推断等价关系。在函数式编程的范畴里来说,Prolog产生的一切动作都是边际效应。这比一般的指令式编程还极端,指令式编程还能构造出具有引用透明型的函数呢。
事实上类似地能够得到很多结论,并且结论能够彼此互证,就好像热力学第二定律的不同表述一样。但是为了首尾呼应,我只陈述一句话:
Prolog的递归只能是尾递归!
arXived
2010年11月2日星期二
Erlang笔记
需要进一步整理。
Erlang主要分为两部分,首先简要总结语言形式的特点和代码写作风格,然后总结配置Erlang时的种种问题。
Erlang主要分为两部分,首先简要总结语言形式的特点和代码写作风格,然后总结配置Erlang时的种种问题。
Log #2
0001
我又搞错了,Mathematica中的东西在Erlang里面基本无法实现。毕竟两者基本的设计目的就有很大差别。今天重点看过了 Programming Erlang的第八、九、十章,然后草草地翻阅到了第十六章。八九十章着重介绍如何创建和消灭进程,以及进程之间的通信。从这里开始Erlang的神奇之处就逐渐显示出来了。一个Erlang进程,包含了如下信息:
1)进程标识
进程标识是在进程创造出来的时候分配的,这个标识是由创造进程的spawn表达式返回的值,这个值必须同一个变量相关联,否则就会被当做垃圾回收掉。
2)它会接收怎样的信息
之前所写的Erlang代码,都是我们从控制台发送的消息。当这段程序执行完毕之后,执行(对应英文是"evaluate"而不是"run")的结果返回到控制台。但是如果需要这个结果的不是我们而是另一个进程呢?于是进程之间就需要以某种方式传递信息。虽然说通信是接收消息和发出消息共同构成的,但这里只说“接收”而非“发出”,原因在于一个进程必须接收消息才能执行,否则就会被Erlang虚拟机当做垃圾回收掉。但是接收到信息之后,进程可以只进行运算而不发出消息,当然也可以发出信息,也有可能立刻死掉(如果这个消息就是要终止这个进程的话)。
更具体地说,一个进程其实就是一个应激规则库,库中每一个规则包含一个模式和对应的行为。这个模式用来匹配它所接受到的消息。由于消息本身是用Erlang的数据结构表示的,故此处的模式就是匹配这些数据结构的模式。具体表示如下:
receive
MessagePattern1 [when is_Predicate()] ->Action1;
MessagePattern2 [when BooleanExpression] ->Action2;
...
end
其中MessagePattern i 就是用来匹配消息的模式。when是一个关键字,when后面是求值后能够返回一个真值的表达式。它可以是Erlang中的谓词函数,也可以是布尔表达式。模式匹配的结果要保证when后的表达式值为真。
这其实就是人工智能中所提到的agent,它是由一系列的if-then构成的应激机制。然而agent只是一种概念,因为if-then中的if有可能是通过类似Erlang的消息机制传递进来,也可能需要通过agent本身去学习。
3)生存周期
生存周期是有两种方式定义的。一个是作为receive的一个备选项,一个是将receive置入一个递归函数中。作为receive的备选项是 after,它提供了当进程因为某种原因在可接受的时间内没有接收到任何消息时采取的措施。erlang内部设置了进程接收消息的一个时间,在这个时间内如果此进程没有接收到消息进而产生任何动作,那么这个进程就会被挂起,虚拟机会去响应其它进程,直到这个进程收到消息为止。有了after,进程就会在超时之后产生一个和接收到消息一样的动作,然后被终止掉,等着garbage collector来收尸体。
另外一种情形涉及到进程的常用写法:
loop()->
receive
MessagePattern1 [when is_Predicate()] ->Action1; loop();
MessagePattern2 [when BooleanExpression] ->Action2; loop();
...
end
这就意味着,这个进程的应激机制被放进了一个无限递归的无参数无返回值的函数中。当它执行完某个action之后不会消失,而是一直留在内存里等待它的戈多。需要注意的是,这里的loop是一个尾递归。书中对尾递归做了很简单但是相当精辟的陈述:尾递归就是当前一次迭代产生的表达式经过求值之后而非压栈之后才进入下一次迭代,如果这里是线性递归,由于没有终点因此当前的表达式会一直压栈而不会执行。我以后会写尾递归专题文章,这里就不赘述了。
4)死亡信息
就像人一样,有的人离开这个世界之前会大声呼救,有的人离开这个世界时就像我爸一样静悄悄。Erlang进程在被回收之前也会有类似的呼叫行为,它会产生一个信息向所有其他的进程广播,不过当然呼叫不呼叫全看这个进程怎么定义,其他程序也可以定义听见或者听不见。以下情况需要特别讨论:
a.誓死盟约link()
在一个进程Prog1中可以通过link(Prog2)将两个进程建立这种“誓死盟约”。如果Prog1是正常死亡,比如是完成任务后通过返回一个 normal信息或者执行exit()“寿终正寝”,那么Prog2会收到这个信息但不采取任何行动(当然或许听到这个消息会有点伤心)。但是如果 Prog1产生了一个错误比如除零,或者产生了其它任何一种不是正常终止的消息,那么Prog2也会跟着自杀。
b.独当一面的process_flag(trap_exit, true)
一个进程可以利用process_flag来把自己改造成强健的“异常退出捕获”进程,或者叫做系统进程。系统进程承担起汇报同在誓死盟约中的其他进程的死亡信息,因此首先一般的死亡讯息不会令系统进程自杀,而且当系统进程捕获到一个死亡信息之后,便不会再把此信息广播给其他进程。
c.鬼来电kill
kill是一个标识而不是一个操作,如果一个进程返回的是kill,那么誓死盟约内的所有进程(包括系统进程)都会自杀。
在这里需要补充几点:
1.消息的收发是异步的
这一点决定了消息之于进程并不相当于参数之于函数。消息不是进程的参数,因为如果是这样的话,在进程正在执行某个消息对应的动作时会忽略掉这个时候收到的其他消息。这就好像我坐火车跑到上海去找你聊天,结果你却跑到杭州玩去了。而在真实世界的容错系统中,我到上海找你而你恰好在的情况更不常见。消息并不是直接发送到进程中去,而是留在了进程的信箱中(一个队列)。receive其实是在一封一封地读收件箱里面的信,然后按照信中说的去执行对应的行为。异步确保了系统的容错性,这是并行系统的精髓。
2.进程是可以注册的
这个其实就是把进程标识和一个原子相关联。但这其实没什么用,因为进程标识本身就是变量的值,那个变量就应当说明这是一个什么进程,不需要多此一举了。
好,就语言上来说,基于一个Erlang虚拟机的并发系统就没有问题了。
0010
接下来就是虚拟机本身设置的问题,目前的多进程仅能在一个虚拟机的支持下实现,如果想要在不同计算机上的两台虚拟机上实现,就需要配置虚拟机的 TCP/UDP端口。这些我还没有仔细看,因为关于网络协议我还有很多东西不懂。但是看到这里我突然意识到一个严重的问题,我之前对 Mathematica的了解也仅限于Mathematica语言本身,但是关于MathKernel,也就是Mathematica虚拟机的使用方法却几乎一无所知。大概在十年前,Roman Maeder就已经弄出了不同MathKernel通信的解决方案。事实情况是,仅知道抽象语言,而不知道虚拟机的工作方式,或者至少是虚拟机的使用方法,是没有办法真正实现产品的。然而Mathematica关于这方面很少有公布出来的文档,这很让人郁闷。
0011
热代码替换。这是一项freakin到家的技术,并且几乎只有Erlang能轻松实现。热代码替换指的是服务端脚本运行时将它进行修改,服务端程序会把收到的包含替换代码的消息直接纳入到自己的身体里!这就是说,服务器在进行维护的时候不需要停机,像做解剖手术一样重新把服务器端代码编译一遍,而是在设计的时候就为它创造一个消化系统,使得它可以一边工作一边汲取营养。为什么IBM等大公司没有应用这个技术?这不是因为前瞻性,而是因为传统。因为爱立信是做交换机起家的,尽管不是计算机公司,但是它却比所有的计算机公司都先接触并熟稔于并行架构。抱着Java不撒手的IBM现在傻眼了。此外,erlang 也有动态执行功能,它可以生成一个文本,然后再将这个文本按照erlang代码执行,和Python的exec()是一样的。昨天木鱼提到的进化代码,现在已经具有实现的基础了。如果服务器能够在互联网上搜索代码和信息并补充道其本身的代码中,挖哈哈哈,那可就太恐怖了。
0100
好,那么明天开始补充一下网络知识。然后去找关于MathKernel的内容。如果了解MathKernel间的通信方式,我知道怎么设计基于MathKernel的热代码替换。另外继续看其他库的源码。
0101
欧了。
-EOF-
Log #3
今天事情还没做完按说是不应该上来写日志的,不过我注意到Wolfram耍了一个很奸诈的诡计,就是混淆了“并行计算”和“分布式计算”的区别。
并行计算和分布式计算的共同点是它们都需要很多计算节点来分别完成一个大问题中的各个部分。这些计算节点可能在局域网里不同的机子上,也可以在同一个机子里不同的CPU核上,也可以在同一个CPU核里不同的进程中,总之计算节点是一个抽象的概念。
那么并行计算和分布式计算有什么区别呢?并行计算指的是所有计算节点彼此相互连接,存在传递消息的通路,而分布式计算中的计算节点是存在阶级矛盾的,其中主节点用来将任务拆分成子任务,然后发送给从节点。从节点解决完这个问题之后再去从主节点那里领取一个新的任务,但是从节点之间却并无通信。
M@7 中的ParallelEvaluate更确切地应该叫做DistributedEvaluate,它把Kernel间的runtime通信封装在了 ParallelEvaluate这个函数中,对于用户来说消息传递的过程是不可见的。在M@2.2中事实上还保留了不同Kernel间应用TCP协议进行通信(那个时候还没有多核CPU),在新闻组中翻到了一篇写于1996年的文章,提到了KernelLink的用法,但是在Wolfram官方网站上,这部分文档已经被删除了。
现在的问题在于,如果想应用M@Kernel来实现类似Erlang的并行编程(注意M@中的表达式都是顺序执行的,同一个Kernel所执行的任务之间是没有通信的,因此它不能叫做“并发编程”(concurrency)而只能叫做“并行”),而Kernel又是独立的操作系统进程,Kernel之间的通信就只能通过MathLink来实现。因为M@Kernel无法向控制台返回一个二进制流然后再被调用它的程序捕获,并且即使可以这样也会很慢。
接下来要去看MathLink的文档,当然Programming Erlang昨天没有仔细看的地方今天要看完。
欧了。
P.S.
经济基础决定上层建筑,而经济基础是由具有消费能力的人的需求决定的,而具有消费能力的人的需求是由技术创造,也是由技术满足的。因此,上层建筑高度依赖科技。尽管续大师从之前非常嚣张地同我天朝对抗,到现在开始一边谄媚校内管理员一边进行无力的暗讽,但事实上这些话都是球用也不顶。想要改变什么,你必须要去做些什么,甚至可以说做任何事情都可以,但是必须去做。金胖子向全世界送出导弹,要比向全世界送出主体思想更有说服力。我们现在的整个局面,其实都是由技术基础决定的。大到花40M弄出来的屏蔽软件,小到四六级考试时黄豆大的无线耳机,什么法律,什么规则,全都轻而易举地颠覆了。以后随着技术的发展,无论是政治还是人伦,一切都会不断变化。
我们无从知晓历史,因为记忆是不可靠的。我们也无从了解未来,因为我们无法了解现在。这个世界在走向混沌,我们首先需要关注的自己的生存。你我不必就整个时代发表评论,因为这个时代本身就是由你我的饮食起居影响着。
-module (server).
-export ([start/2, rpc/2]).
%%Name of the module is "server", "start" and "rpc" are the functions can be call externally.
start(Name, Mod)->
register(Name, spawn(fun ()->loop(Name, Mod, Mod:init()) end )).
%%Here the Mod is the name of the module which communicates with the new created process. Name is the name of the new created process.
rpc(Name, Request)->
Name ! (self(),Request),
receive
{Name, Response}-> Response
end .
%%RPC is the remote processing control. the framework is defined in the server module, but the messages which to be sent via RPC are defined in the module below. RPC sends the Request to the process with name Name. self() indicates the process sending message via RPC. the Response sent back from the process with name Name will be passed to the process which uses RPC.
loop(Name, Mod, State)->
receive
{From, Request}->
{Response, NewState}=Mod:handle(Request, State),
From ! {Name, Response},
loop(Name, Mod, NewState)
end .
%%loop is the main body of the new created process. When it received the message via RPC, it will start an action defined in the module below. After preformed the expected action, it will send the Response back to the process, which indicated by From.
This is the core of a server, the module neither defines any acceptable information from client, nor specifies how it behaves. It only create a process with requested name, and initiate it as requested. It also defined a communication framework, so that the client can send message via the framework to access the new created process. Finally, it defines the content of the new created process, which indicates that once the message received, it will act as requested, send a response to client process, and then stand by for next message.In a word, the server is only an executive, which has no prior information about what to hear and what to execute. Just like a main() in C.
-module (name_server).
-export ([init/0, add/2, whereis/1, handle/2]).
-import (server,[rpc/2]).
%%it inherits the definition of RPC from the server module, and has 4 function can be called externally.
%%Client routines
add(Name, Place)->rpc(name_server, {add, Name, Place}).
whereis(Name)->rpc(name_server, {whereis, Name}).
%%Defines the message corresponding to the action to be sent.
%%call back routines
init() -> dict:new().
handle({add, Name, Place}, Dict)->{ok, dict:store(Name, Place, Dict)};
handle({whereis, Name}, Dict)->{dict:find(Name, Dict), Dict}.
%%Defines how the server process behaves.
Client provides not only instructions, but also the introductions to those instructions.
Programming Erlang那本书里面举了一个看起来简单到必须得一次成功的分布式计算的例子,但是我却为这个例子花了将近三天时间。原因是这本书首先假定你处于一个理想情况并且能够顺利地把它实现,之后才零零散散地想到如果你哪里会有问题该怎么解决。当然正确的状态只可能有一个,但是错误的情况却可能层出不穷,解决问题需要一个社区的努力。
但是通常情况下,如果你不知道这个问题的答案,通常是因为问题出在更低一级的层次,并且你甚至不知道那里出了问题。这和写程序不一样,写程序是想好之后才动手,但是解决配置问题只有你动手之后才能开始想。然而从给定的条件下找原因并最终解决问题,这样的事情我很少做,以后需要多经历这样的事情。
1)
书上的例子使用了一个rpc:call/4。正确的情况如书中所示,在认真抄书没有拼写错误,以及没有忘记先输入kvs:start()的情况下,你可能会收到一个错误应答叫做{badrpc, nodedown}。它是说节点没有连接到位。那么你你需要使用net_adm:ping(node@Hostname)试验一下。根据你之前得到的结果,ping的结果很可能是pang。注意pang是失败的回应,成功的回应是pong。
2)
如果你在得到pang回应的时候有一段时间console不响应输入,而另外一台机子的console上面没有任何反应,这说明发出ping请求的节点就根本没有找到另外那个节点所在的机子。所以,你需要在操作系统的shell下再去ping那台机子。如果操作系统下就没有ping成功,那可能需要考虑网络设置,比如防火墙是不是把ping包拦下来了。[我曾在这里花了一上午才发现是瑞星干的]
3)
注意还有一种可能,就是ping IP地址成功了但是ping机器名称没成功,那是因为机器名和IP地址没有关联。关联的方法是编辑机内的hosts文件。windows平台这个文件在system32/drivers/etc里,而*nix在/etc里。把机子的IP和hostname关联起来,在操作系统shell去ping,成功后再到Erlang下面去ping。
4)
注意,即使这样仍然没成功,可能出现的情形是仍然返回pang,但是速度比之前快了很多,并且被ping的节点提示一个error report,说外来的访问被禁止了。很好,这下问题就明朗了,剩下的问题是cookie。cookie的内容是一个atom,算是不同nodes之间通信的一个暗号。只有拥有相同暗号的nodes之间才能进行通信。cookie的设置可以在erl的参数中设置,也可以进入erlang的console之后用erlang:set_cookie(node(), cookie)进行设置,并用erlang:get_cookie()进行查询。
5)
即使这样仍然可能不成功,也许是因为erlang的环境没有设置正确。在linux下安装erlang包后环境变量就设置好了,但是在windows中却需要自己重新设置。erlang可以通过code:add_patha(z)添加查找module存放的目录,但需要小心的是它们是已经编译过的beam文件而不是erl源代码。erl文件erlang只能在操作系统调用erlang的当前目录下进行查找,而不会到那些预设的目录中查找erl文件。
如果你使用的是eclipse+Erlide是不会遇到这些问题的,你不需要手工把erl编译成beam,IDE都帮你做好了。
好了,infrastructure到现在为止就算建立成了,今天晚上喝瓶啤酒庆祝一下。 : )
订阅:
博文 (Atom)