Home > study > 回“老赵”关于“Erlang中最大的问题”

回“老赵”关于“Erlang中最大的问题”

July 10th, 2009 :: jackyz

活跃在博客园的“老赵”,是一位研究 .NET 非常深入的同学(因为我本人也是老赵——jackyz.zhao,所以,特地加了引号)。他最近很关注“在 .NET 下实现 Erlang 语言特性”的课题,并为此写了一系列的技术文章,相当不错,我一直都在关注。他自己写了一个名为 ActorLite 的小东西,此前做过介绍,是个不错的尝试。

最近“老赵”同学写了一篇《一种适合C# Actor的消息执行方式(上)》,其中提到“(在消息执行上) Erlang 中最大的问题”。这是一个很有意思的观点,而且因为富于代表性因而显得很有价值,很有必要拿出来和大家探讨。

其中提到:

Erlang的优势与缺陷

  Erlang在消息执行方式上的优势在于灵活。Erlang是弱类型语言,在实现的时候可以任意调整消息的内容,或是模式的要求。在 Erlang进行模式匹配时往往有种约定:使用“原子”来表示“做什么”,而使用“绑定”来获取操作所需要的“数据”,这种方式避免了冗余的cast和赋值,在使用的时候颇为灵活。然而,世上没有完美的事物,Erlang的消息执行方式也有缺陷,而且是较为明显的缺陷。

  首先,Erlang的数据抽象能力实在太弱。如果编写一个略显复杂的应用程序,您会发现程序里充斥着复杂的元组。您可能会疲于应对那些拥有7、 8个单元(甚至跟多)的元组,一个一个数过来到底某个绑定匹配的是第几项,它的含义究竟是什么——一旦搞错,程序便会出错,而且想要调试都较为困难。因此,也有人戏称Erlang是一门“天生会损害人视力的语言”(令人惊讶的是,那篇文章居然搜不到了,我们只能从搜索引擎上看出点痕迹了)。

  而我认为,这并不是Erlang语言中最大的问题,Erlang中最大的问题也是其“弱类型”特性。例如,现在有一个公用的Service Locator服务,任意类型的Actor都会像SL发送一个消息用于请求某个Service的位置,SL会在得到请求之后,向请求方发送一条消息表示应答。试想,如果SL的功能需要有所修改,作为回复的消息结构产生了变化,那么我们势必要修改每一个请求方中所匹配的模式。由于消息的发送方和接受方在实际上完全分离,没有基于任何协议,因此静态检查几乎无从做起。一旦遇到这种需要大规模的修改的情况,Erlang程序便很容易产生差错。因为一旦有所遗漏,系统便无法正常执行下去了。

这的确是对于动态类型语言很常见到的担心,而且,确实,如果不加注意会成为严重的问题。这种困扰确实是因为 Erlang 的“动态类型”和“基于消息”而造成的。但,这并非无解,实际上,在 Erlang 编程规范之中,已经给出了解决方案。

通常而言,Erlang 中的消息应该是以“控制流”为主,在消息的数据结构表达上,不同的消息通常都对应着不同的处理流程,在这里“控制”是消息中最为关注的内容。为此我们可以简单的引入一个 atom 来予以表达,这就是 Tag Messages 的最佳实践:

5.7 Tag messages

All messages should be tagged. This makes the order in the receive statement less important and the implementation of new messages easier.

Don’t program like this:

loop(State) ->
 
receive
    ...
    {
Mod, Funcs, Args} -> % Don't do this
      
apply(Mod, Funcs, Args},
      
loop(State);
    ...
 
end.

The new message {get_status_info, From, Option} will introduce a conflict if it is placed below the {Mod, Func, Args} message.

If messages are synchronous, the return message should be tagged with a new atom, describing the returned message. Example: if the incoming message is tagged get_status_info, the returned message could be tagged status_info. One reason for choosing different tags is to make debugging easier.

This is a good solution:

loop(State) ->
 
receive
    ...
    {
execute, Mod, Funcs, Args} -> % Use a tagged message.
      
apply(Mod, Funcs, Args},
      
loop(State);
    {
get_status_info, From, Option} ->
      
From ! {status_info, get_status_info(Option, State)},
      
loop(State);   
    ...
 
end.

可以相信,一段庞杂的代码,在经过一番这样对 Message 本身的设计和重构之后,最终都能让其恢复“秩序与美感”。而消息中的其他参数,如果其处理逻辑非常复杂的话,那么带有模式匹配的子函数,又或着是 Tuple/Record 正是其用武之地。

上面我们提到了“对 Message 的设计和重构”,实际上,这只是问题的一个方面。另外一个方面是:无论 Message 的“协议”设计得有多精巧,其对外的接口都不应该用消息来表达,而应该是“接口函数”的形式。

Erlang 系统之中的消息是极度灵活的系统,但它并不适合用来作为模块之间的接口,因为它过于灵活。更加适合这一角色的设施是“函数”。用来充当这样角色的函数就被称作“接口函数”,在其实现代码中,除了“按照约定的格式发消息以外”什么别的也不做——因为它包装的正是以消息为载体的交互“协议”。它是函数,有着严格的语法检查,而且可以在多个模块之间重用。一旦需要修改消息协议,起隔离作用的“接口函数”就会体现出其存在的巨大价值。

5.10 Interface functions

Use functions for interfaces whenever possible, avoid sending messages directly. Encapsulate message passing into interface functions. There are cases where you can’t do this.

The message protocol is internal information and should be hidden to other modules.

Example of interface function:

-module(fileserver).
-
export([start/0, stop/0, open_file/1, ...]).
 
open_file(FileName) ->
 
fileserver ! {open_file_request, FileName},
 
receive
    {
open_file_response, Result} -> Result
 
end.
 
...
code...

Erlang 是动态语言,对其进行静态检查比较困难(并非不能 R13 已经有所动作)。但,并不是说没有静态检查就会寸步难行。Erlang 同样重要的语法特性是它还是函数式语言,遇到有疑惑的地方,不妨以函数的角度来思考。

感谢 Arbow 的分享。感谢 JeffreyZhao 的写作。

Erlang 语言素有“难学”的名声,其中一个原因就是因为虽然其语法十分简单,但常会让人心生 “就这样了,然后呢?” 之惑——因为过于灵活而无所适从,而且也不存在着显而易见的正确用法。这种困扰通常要在有了一定的实践经验之后才会渐渐消散。换句话说,在“学会”和“用好”之间存在着一堵模模糊糊的墙,而这一“未知区域”又需要一定的耐心和经验方可穿越。

study , ,

  1. July 19th, 2009 at 00:38 | #1

    dsun :
    》我深知不是每个人的逻辑能力都是一样的,也不指望所有人都一样刻苦,但是,
    》学无止境,这也是我还耐心待在这里的原因。
    您的意思是您逻辑能力最强,您最刻苦,您砌了这么高的楼就是为了显示您不耻下学或者诲人不倦?您说这话有意义吗?

    如果这里是你我两个人的话,是:-)

  2. trustno1
    July 19th, 2009 at 01:25 | #2

    至于STM这个东西,目前理论界的一个普遍共识这玩艺是还是不那么靠谱.一个原因是系统规模变大以后,有很大机会出现starvation,这个东西从目前来看貌似是无解的问题.
    第二个是这个需要使用类型系统来严格的隔离side effects.也就是说使用STM最为关键的仍然是如何处理side effects.但是side effects它就不是简简单单的一个immutable 变量.我以前一直强调过side effects就是io,mutable变量实际上只是IO的一种,不是说你做到了immutable就完事了就彻底隔离IO了.比如说一个socket.write 函数你是否允许在STM的transaction中使用?如果允许使用?你怎么做retry语义?如果不允许,那么你如何让编译器识别出socket.write,以及使用了这个函数的所有的自定义函数?为了做到彻底隔离IO就需要彻底的改造类型系统,Scala那种半吊子的类型系统是搞不定的.就算你能在scala里面搞定,你引入的java库怎么办呢?也全部改嘛?我觉得没有彻底隔离IO的语言采用STM其结果大致和C++的那种没有GC支持的try/catch一样危险.

    即便是在分布式系统上现在的STM仍然是以message passing为基础实现的.这点在Joe Amstrong的blog已经提到了一个非常简单的实现.所以在我看来,STM不是Scala的比较特殊的亮点.以目前scala目前类型系统上来说,它实现的STM几乎用Erlang实现的(如果真的要做的话)没有太大的本质区别.

  3. trustno1
    July 19th, 2009 at 01:52 | #3

    另外,07年lambda-the-ultimate上有一段针对message passing和STM之间的讨论贴子.http://lambda-the-ultimate.org/node/2048.
    对STM的看法上,基本是和Lock差不多.能犯的错误它都能犯特别是没有强力的类型系统情况下.另外当中有一个牛人透露了他们公司在JVM上实现STM的方法.
    In essence, our technology IP here is that we optimize the standard Sun Java virtual machine and make it transactional, i.e., we can transparently fetch and store objects from disk and let multiple threads and processes share data in a consistent manner.
    不直接修改JVM,直接在bytecode乃至在更高层的scala里实现可用的STM几乎是没可能的.

  4. July 19th, 2009 at 02:03 | #4

    trustno1 :
    至于STM这个东西,目前理论界的一个普遍共识这玩艺是还是不那么靠谱.一个原因是系统规模变大以后,有很大机会出现starvation,这个东西从目前来看貌似是无解的问题.
    第二个是这个需要使用类型系统来严格的隔离side effects.也就是说使用STM最为关键的仍然是如何处理side effects.但是side effects它就不是简简单单的一个immutable 变量.我以前一直强调过side effects就是io,mutable变量实际上只是IO的一种,不是说你做到了immutable就完事了就彻底隔离IO了.比如说一个socket.write 函数你是否允许在STM的transaction中使用?如果允许使用?你怎么做retry语义?如果不允许,那么你如何让编译器识别出socket.write,以及使用了这个函数的所有的自定义函数?为了做到彻底隔离IO就需要彻底的改造类型系统,Scala那种半吊子的类型系统是搞不定的.就算你能在scala里面搞定,你引入的java库怎么办呢?也全部改嘛?我觉得没有彻底隔离IO的语言采用STM其结果大致和C++的那种没有GC支持的try/catch一样危险.
    即便是在分布式系统上现在的STM仍然是以message passing为基础实现的.这点在Joe Amstrong的blog已经提到了一个非常简单的实现.所以在我看来,STM不是Scala的比较特殊的亮点.以目前scala目前类型系统上来说,它实现的STM几乎用Erlang实现的(如果真的要做的话)没有太大的本质区别.

    Erlang中一个很好的地方是一套仔细设计过的IO库,特点是启一个或若干个process来与调用者通过message passing交换数据。我很希望看到Scala中的IO库也全部重新仔细设计。

    Scala对Java库的调用虽然有潜在危险,但在实际应用中似乎只有少数情况下要特别注意。

    Scala官方对于STM还没实现,但可能会实现一个,对合适的问题还是可以一用的。Fortress和Clojure重点倒是在STM上。我对最近出现的许多Java实现的分布式文件和Cache系统很好奇,其中有些看上去象一个通过message passing来协调/同步的大大的层次化的STM。

    我最近的实际应用中,主要处理大规模的并行运算和分布式Cache,我的体会是,为了计算速度,Scala的Actor和传统的Lock模式都用,各自扮演不同的角色,发挥各自的长处。当然,对设计的要求相应要提高。我的观点是系统可以演变成由内部可以共享状态的团块,但这些团块之间相对隔离并通过消息传递组成的更大层次的团块的形式。

  5. dsun
    July 19th, 2009 at 09:52 | #5

    @Caoyuan
    哈哈,您自我感觉太好了。不过,我咋觉得这话不像是一个认为学无止境的人说的,倒更像是出自一个无理强争三分的气急败坏的人之口。

    英雄没有自封的,技术探讨是严肃的,搞人身攻击无疑于泼妇骂街,请自重。

  6. trustno1
    July 19th, 2009 at 10:03 | #6

    Clojure的STM仍然是需要靠程序员保证 transaction中不出现io.他有一个IO!的宏可以辅助检查.但是这就如我前面提过的C++ try catch特性。没有GC的情况下C++的try catch特性是非常危险的.虽然C++的拥趸们经常说他们可以通过RAII来帮助程序员屏蔽这些问题.但是你可以想象一下在一个大规模的工程里,会出现各种各样RAII这这种粗糙手段无法防止的事情.比如你引用了某个第三方的lib,如果这个lib本身没有对try catch引起的内存问题加以处理。然后你又在上面做了层层的包装.当你把它置于try catch下的时候就会出现莫名其妙的问题.
    没有对IO进行类型检查的系统里面.STM可以当作一个花销的摆设,但是绝对派不上用场.这点其实在Erlang当中也存在,比如mensia的transcation就不允许你在其中使用IO.但是mensia相对而言都比较好一些,因为毕竟是数据库属于专业模块不会对程序的全局产生破坏影响.但是STM是一个全局使用的特性,这就需要非常非常严格的检查而不是把这种事情扔给程序员.
    Currency和Parallesim是两回事.Erlang本身就不是为计算密集型的Parallesim应用设计的.因此在这个平台上谈论针对计算密集型的处理方法有点文不对题.

  7. July 19th, 2009 at 20:22 | #7

    trustno1 :
    Clojure的STM仍然是需要靠程序员保证 transaction中不出现io.他有一个IO!的宏可以辅助检查.但是这就如我前面提过的C++ try catch特性。没有GC的情况下C++的try catch特性是非常危险的.虽然C++的拥趸们经常说他们可以通过RAII来帮助程序员屏蔽这些问题.但是你可以想象一下在一个大规模的工程里,会出现各种各样RAII这这种粗糙手段无法防止的事情.比如你引用了某个第三方的lib,如果这个lib本身没有对try catch引起的内存问题加以处理。然后你又在上面做了层层的包装.当你把它置于try catch下的时候就会出现莫名其妙的问题.
    没有对IO进行类型检查的系统里面.STM可以当作一个花销的摆设,但是绝对派不上用场.这点其实在Erlang当中也存在,比如mensia的transcation就不允许你在其中使用IO.但是mensia相对而言都比较好一些,因为毕竟是数据库属于专业模块不会对程序的全局产生破坏影响.但是STM是一个全局使用的特性,这就需要非常非常严格的检查而不是把这种事情扔给程序员.

    Scala本身并不是pure functional的语言,对Side effect没有(也许很难)作全面的封装。我不清楚能否在对Scala的IO系统仔细设计和封装后做到对IO进行类型检查。不过,“没有对IO进行类型检查的系统里面,STM可以当作一个花销的摆设,但是绝对派不上用场”似乎也太绝对,至少,在程序员自己能处理的情况下,把STM限制在系统的某些地方,也还是可以一用的。

    Currency和Parallesim是两回事.Erlang本身就不是为计算密集型的Parallesim应用设计的.因此在这个平台上谈论针对计算密集型的处理方法有点文不对题.

    假设,大部分人都同意:“Erlang本身就不是为计算密集型的Parallesim应用设计的”。然后,把问题换成:“Erlang能不能用来做计算密集型的Parallesim应用?”,答案就好像没那么直观了。理论底子厚的,可能想一下就明白了,象我这种底子薄的,就得拿几个例子验证一下,然后苦思冥想几个月,似乎明白了点,然后就跑到jackyz的场子里大喊大叫:-),为什么?高兴呗。

    接着,看看这个话题的另一面。如果我说,“现在的Erlang**只**适用于容错的、大规模的、软实时的短信息交换”,估计很多人就开始反对了。如果我还说,“JVM上现在也能很**方便地**实现容错的、大规模的、软实时的短信息交换”,那就更不得了。为什么?JVM不是为“容错的、大规模的、软实时的短信息交换”设计的?同样,我又得拿几个例子验证一下,然后苦思冥想几个月,似乎想明白点,就跑到jackyz的场子里大喊大叫了。

    事情就是这样子的。

  8. dsun
    July 21st, 2009 at 09:08 | #8

    1、企业应用中基本上没有什么真正意义上的计算密集型应用(像:图像、信号处理,大规模科学计算等)。即使有些计算方面的处理,更应该考虑的是并行编程和控制模型的简单性和伸缩的容易性,因为过不了几年,具有上万核,甚至百万核的机器估计就会出现,能够容易地编写出无需更改就可以自动的应用这些多核的能力的程序更重要。即使是进行真正的计算密集型应用,Erlang作为一种coordination language还是很合适的。

    2、share nothing不是靠immutable保证的,而是靠process独立内存空间加上copy semantics保证的。copy semantics不是immutable的实现方式。immutable是语言中变量的语法特性,copy semantics是语义动作,完全风马牛不相及。只要是复制语义的,即便不是immutable的照样不会破坏share nothing。

    从严格意义上讲,完全在语言层面上满足copy semantics的语言只有Erlang一个,即使是像binary的reference在语义上也可以认为是拷贝的,并且其存在于独立的heap中。另外,对ETS的访问在语义上也是IPC性质的。真正在语言层面上做到share nothing的只有Erlang一个。Scala在语言层面上不具有copy semantics,而只是能够定义immutable的变量,因此严格地讲,Scala不是share nothing的,而是最多可以做到share immutable。要在Scala中做到看上去的share nothing,必须靠设计约定获得。

    3、语言越来越复杂(尤其是平台基础和上层特性不匹配时)不是好现象。

  9. max fung
    July 24th, 2009 at 13:49 | #9

    大部分都没看懂,这是什么呀~好多英文泥~
    讨论语言的好坏?这个我最爱看,可以充分展现程序员勇猛的一面
    讨论实际应用?在模拟炒股大把赚钱的人,在真实炒股中也会赔得很惨。

    既然不知道什么跟什么,我也来吐两句,用普通人的普通话

    语言好坏:说容错性与动态代码替换,世界上最强的莫过于as2(actionsrcipt 2.0)Adobe搞的as3简直是大粪,而我又不得不用大粪,郁闷!as2是力与美的结合,它几乎完全不出错,即使出错也可以选择优雅的忽略或善意的提醒,而程序还是很正常的运行滴~跟没错误一样。动态代码替换,哪家伙简直是。。。。强tnnd强!把代码封装在swf中独立开来,运行中读取一下就是新的代码,super!抽象方面,我的神哪,你可以用诸如老子,小子关系的类编程,也可以用函数式编程,交叉使用(用句广告语,中西药结合疗效好!)那效果,刚刚的!界面设计那更没得说,哪个语言有这样的界面设计方式,一个字:华丽!(翻译成英文就是一个字啦)并发,那“孙子”(此处不是贬义,下面分解说明)简介好得不得了!运行时在库中实例化一个movieclip,就是一个孙子,每个孙子都是“并发”运行(秒级)。

    实际应用:鉴于以上的好处,在实际开发中我是前端用falsh + mdm(air?烂货!不支持as2,当然mdm也烂,不过凑活啦)开发页面和一般应用,后端(这里的后端也是前端,呃怎么表述?~)用其它语言,最近用erlang,解决速度和特殊需求等问题。例如某个小小型财务软件,前面flash,后面erlang并行运算,那个叫爽~!
    又如,本地数据库与网络同步,前面flash,远程erlang并行运算,那个叫赞~!

    现实中,这种实际运用最厉害的应该算香港人,一句话 粤语英文各半,方便的解决了语言功能不足所带来的问题,当然了,开发一下语言的插件也可以,例如 M 叫 兆,搞定。erlang 字符处理不足,开发一个插件就可以啦 ~ ,但是,插件并不是人人知道的,你推广谁知道,M就是兆?

    期望erlang被某家大大企业收购,并继续开发,到时所有弱点就会慢慢解决,故事都会这样发展的~

    现在是可选的东西太多,看不清,在以前只有apple II和6502这种语言可选的话……

    是好东西就用,不足就补足,这种方式行不通就那种,单一不行就综合,此乃中庸之道也~没有实践试验,纸上谈兵不足道也~

    以上p文 至此结束。

  10. kaka
    July 30th, 2009 at 16:22 | #10

    对啊,从常规语言,尤其是被认为在常规语言注入.net之类的上面很深入研究过的人,转型Erlang的时候都会有些 culture shock。关于在消息交互中的动态类型问题这在Erlang这个新语言环境有新的编程规范有新的思维pattern。哪个语言都有其固有陷阱或者缺陷什么的,如果难以从语言层将其完善,有其特定的编程规范来使得大家不掉进这个坑里面不就好了,就像楼主说的,可以有个tag来匹配的。

  11. May 27th, 2010 at 16:01 | #11

    Hello, blogger, your article very good, looking forward to sharing your latest

  12. July 28th, 2010 at 13:27 | #12

    DMOZ Directory or ODP is a human managed directory that sends results or data to search engines. DMOZ directory data is now used by the majority of major search engines on the net.

  13. April 13th, 2011 at 02:07 | #13

    Tuocdohnw! That’s a really cool way of putting it!

  14. huaqiao
    April 19th, 2011 at 18:01 | #14

    上海现在哪家需要erlang开发的人。哈哈,我可以毛遂自荐。3年erlang项目开发经验,参与过的开源项目有:ejabberd,yaws,erlyweb,mochiweb,rabbitmq,riak,rebar等,有合适的可以发邮件给我。

  15. erlangfans
    April 20th, 2011 at 23:14 | #15

    huaqiao :分析不错

Comment pages
  1. No trackbacks yet.