Erlang优缺点
做了电商网站,就想学分布式,接触了Hadoop。喜欢上了分布式。后来听说了易语言,就发现自己对当前的语言方向没有了解。于是就查到了erlang。后来又因为开发环境问题,研究了eacms与vim ,然后又从erlang与golang语言的角度分析了语言的优劣和前景。
总的来说。Erlang是近30年前的东西。确实没有golang新。而且不具备一些散列反射等新编程理念的看法。而hadoop也是有近20年的历史了。这样的程序和思路,肯定在某种新的编程技术技巧和特性上。或者像erlang之父所说的一样。在实际大型应用中,搜索,关联,不是编写新的程序,而是找到你的老程序位置,并且进行整合利用。注释文档,元数据,支持IDE,写语言的支持等肯定都有不足。
但是其核心既然在30年后依然被翻出来。而且经过30发展很多大企业,世界级的大项目一直使用并且没有出错。更加近年有很多社区激动活跃去改进和支持。
证明还是非常有价值去学习的。就好像有了C#而不去关注C语言一样。
并且新生的GO语言和淘宝针对hadoop的改进根本还是出于原本核心的思路。明白了根本,新的特性自然也不怕。新的改进目前还是核心,即使发展壮大也得3年,普及有可能需要更久,毕竟从行业需求来看,分布式计算方面不是中游,是毕竟底层的开发。他的发展不会给大学这样的教育和市场的开发带来很大的更替。
等你学会原本的程序,并理解了真正的思路。弄的精通了。甚至你都可以根据你的需要写出更新的。比Go都新的思路来。
所以。从根本学起。加油。不要再犹豫。
下面说一说ERLANG的优缺点吧。
“Erlang 是动态类型的语言,因而不能进行静态分析,所生成的文档也不包含有助于理解的类型信息”——这是惯常的看法,广为流行,而且被看作是 Erlang 在开发大型系统时的一个短板(大型系统意味着更强烈的静态分析需求和更严重的依赖文档进行沟通)。
然而 Erlang 是一个有着 20 多年历史的成熟系统,它早已发展出了一套自己的类型标注系统,不仅用来生成文档,更重要的是可以据此对源码进行静态分析,通过程序来排除一些低级的和隐藏的错误。在这方面, Erlang OTP 的源码本身及其文档就是最好的例子。在 《Erlang 程序设计》的附录A部分,对于这个系统的使用已经进行了充分的说明。
需要强调的一点是在 Erlang 语言的背后还有一个活跃的社区(后者更为重要),其 EPP 过程一直都在持续不断地推进语言本身的进化。这方面最新的成果便是:在 R13 中,将此前文档级的 @spec,@type 标注升级为语言级的 -spec,-type 标注。可以预期的一点是,在未来的版本中,这些方面仍将持续推进。
litaocheng 同学的这篇“Erlang类型及函数声明规格”,风格严谨,论述详尽,涵盖了最新的语言特性,是任何一个程序员想要开发“严肃 Erlang 程序”的必读文档。
说到concurrent,一般会想到Erlang和Go语言,这两种语言的主打特性都是concurrent,Erlang有着20多年的历史,是为简化开发电信大并发和高可靠性应用而发明的语言,Go是Google从2007年开始设计,2009年opensource出来的,Go属于一种system language,opensource的就算这两种语言吧,公司内私有的语言则有TNSDL,SDL的一个变种,以前写过一篇SDL和Erlang比较的文章(http://bookjovi.iteye.com/blog/1233299),这三种concurrent语言各有不同,下面看看:
1)语言设计
Erlang的实现基于虚拟机beam,Go是编译型语言,有着独成一体的compiler(不同于gcc,Go很好的解决了依赖的问题,所以编译go程序时不需向编译c程序那样指定include和library),TNSDL和GO类似,属于native执行。Erlang和TNSDL主要是为电信级应用服务的,而Go的concurrent则更具有通用性,这点主要体现在concurrent的设计,Erlang和SDL是基于process之间传递message,而Go是goroutine组成,再加上channel,Go通过把process和message解耦使得Go的设计更有通用性和灵活性,应用可以根据自己的需求决定是否需要channel的处理。routine加channel的设计在stackless python也有(有些人说Go = C + stackless Python),正是channel的灵活性使Go中不需要Erlang中的PID,Go中的go无返回值,Erlang中的spawn则返回PID。
Erlang没有对message buf进行控制,使用Go的channel则可控制channel的capacity,make(chan int, 100)
2)Library支持
Erlang有着20多年的历史,OTP中各种behavior,driver支持使得使用Erlang得心应手。Go的历史短得多,library仍有欠缺。
3)scheduler调度器
从实现的角度看Erlang中每个process都有自己的heap,所以无法共享内存,总体来说Erlang的scheduler与Linux kernel非常相似;Go和SDL属于native执行,这里主要讨论公平性的问题,Erlang中每个process有reduction,类似于Linux kernel中process的time slice,Go是native执行,那么Go的runtime是如何控制每个goroutine的公平性呢?答案是没有,native执行的goroutine无法像Erlang那样保证公平性,goroutine只能在syscall、io、channel read write操作时才能有机会重新执行schedule函数,以执行其余的goroutine。在concurrent语言中,如不能很好的解决公平调度和优先级调度是个很大的问题。
鉴于Go现在还不是太成熟,或许以后会有改进,scheduler和GC都有很多的讨论,现在scheduler的实现逻辑及其简单(G & M),与Erlang 调度器相比更是简单(里面有些bug,还有公平性的问题)。
Erlang scheduler位于:otp/erts/emulator/beam/erl_process.c
Go scheduler位于:go/src/pkg/runtime/proc.c
4)memory model 内存模型
Erlang中每个process有自己的heap,stack在heap的底部,这里的stack是Erlang process的stack,类似于Java中的操作数栈,stack向下增长,stack top如遇到heap top,则进行GC,GC后stack移到新的地方。
Go是native执行,goroutine的stack是segment stack,TNSDL的runtime也是使用这种segment stack,segment stack就是一个程序中有很多stack执行,stack的切换则通过setjmp,longjmp实现的。(setjmp和longjmp的一个主要应用就是concurrent,另一个是在C中模拟try catch)
5)GC垃圾回收
Erlang的GC属于分代算法,有个old heap,有minor gc和full sweep,总体来说和Java类似,只不过Erlang没有mark的过程,直接根据rootset找到所有Eterm放在新分配的heap的。
Go现在是mark-sweep,以后会使用IBM的低延时GC算法。
6)高可靠性
Erlang中有个builtin的高可靠性,如link和monitor机制。
Go是一个通用性的设计,虽没有built-in的link和monitor,goroutine则可使用defer来实现link的效果。
7)性能
Go的实际目标之一就是高性能,这也是为什么go是一种编译型语言。理论上分析goroutine的性能会好于Erlang的process,但软件设计原则中性能只是衡量标准之一而不是全部。
好了,话太多,就说到这里了,个人还是很喜欢Go的设计,未来程序语言的实际应该像go和Erlang那样扔掉mulitthreading的设计(Java,Python),同时channel的使用使得go的实际更具通用性和灵活性,这是concurrent语言被行业接受非常关键性的因素。
- 深受高人笔下智慧的熏陶,现有一难题困惑,golang 是否比Erlang Scala更加优越,毕竟好多大牛支持这个新兴语言,不知您是否看好google的golang语言,提前谢谢您的回复了~~
- Tim says:
各有各的好处。
如果你不考虑具体的使用场景,建议选择 golang。erlang 的适用范围相对狭窄。
两者目前都重服务端,所以客户端开发来说,都不是特别适合,如果非要用,建议用 golang。
服务端开发,如果团队比较小,喜欢函数式编程,而且网络模块偏重 io,逻辑不复杂,用 erlang 可以获得比 go 更好的性能,目前 go 语言在性能上还没做过非常大力度的优化。其他情况我建议用 golang。
Erlang众所周知,这里不介绍了。其优势在于:
- 最简洁精练的分布式模型
- Node, Process, Mail (Message)
- 最优雅的错误处理模型:速错(Fail fast)
- 如果出现任何异常,立即死掉
- GenServer编程框架
- 程序代码风格完全一致,便于交流
- 轻量级的进程
- 可以尽可能地按照正常的业务逻辑去设计,而不是过多地考虑硬件环境的制约。
- 热部署(Hot swap)
- 变量不可变
- 更容易写出可靠的程序。也利于事务性代码的编写。
- 当然这是一把双刃剑。它也改变了你的编程习惯。
CERL 这个词的来源是因为他被定为为:Erlang Model for C++。目的是在C++中获得Erlang的好处(部分)。不过,实际上CERL被实现为与语言无关的。基于CERL,你可以用PHP、Python之类的语言来写服务器或客户端。
CERL 基于Erlang的分布式模型。实现了Erlang做最重要的几个概念:Node, Process, Mail (Message)。他也支持速错(Fail fast)及GenServer编程框架。当然,也有它没有做到的,如:
- CERL 不支持轻量级的进程。尽管CERL的Process模型和Erlang一致,但是他并不轻量,事实上CERL的进程就是操作系统中的Thread。刚开始我准备用协程,不过后来被证明这是不必要的,所以CERL永远都不考虑协程这东西。
- CERL 不支持热部署。当然,这是指他没有提供机制支持,而不是指你没法做到。
为什么是CERL,而不用Erlang?
刚开始我确实用Erlang,而且完成了DEMO。不过,我最终发现这中间存在些问题。尽管Erlang的思想是非常优秀的,但是:
- Erlang是小众语言,使用它存在维护上的风险。(这个是缺点,但不是我最重要的理由)
- Erlang的变量不可变,改变了程序员的思维习惯。
- 我个人原则上认可变量应不可变带来的好处,但是允许特殊情形下的可变(例如for循环的自变量)。
- Erlang是动态语言。
- 我个人认为大型工程都应该使用静态类型语言,以获得更好的代码质量和性能(静态类型语言更容易写出高质量少BUG的代码)。
其实,最终极的理由是第3条。
CERL简介
CERL对外表现为一个动态库(cerl.dll) + 一个编译器(sdlc)。逻辑上CERL分为两层,底层称为CERL Core,上层为GenServer框架。
- CERL Core –表现为动态库cerl.dll。主要实现Erlang的分布式模型,及其他辅助基础设施。
- GenServer框架。它主要定义了服务器接口描述语言: Server Description Language(SDL),并提供相应的编译器 sdlc 以生成:
- 客户端如何调用该服务器的Proxy代码。
- 服务器端Stub代码。
- 服务器Impl文件的框架代码。
Erlang vs. CERL
Erlang众所周知,这里不介绍了。其优势在于:
- 最简洁精练的分布式模型
- Node, Process, Mail (Message)
- 最优雅的错误处理模型:速错(Fail fast)
- 如果出现任何异常,立即死掉
- GenServer编程框架
- 程序代码风格完全一致,便于交流
- 轻量级的进程
- 可以尽可能地按照正常的业务逻辑去设计,而不是过多地考虑硬件环境的制约。
- 热部署(Hot swap)
- 变量不可变
- 更容易写出可靠的程序。也利于事务性代码的编写。
- 当然这是一把双刃剑。它也改变了你的编程习惯。
CERL 这个词的来源是因为他被定为为:Erlang Model for C++。目的是在C++中获得Erlang的好处(部分)。不过,实际上CERL被实现为与语言无关的。基于CERL,你可以用PHP、Python之类的语言来写服务器或客户端。
CERL 基于Erlang的分布式模型。实现了Erlang做最重要的几个概念:Node, Process, Mail (Message)。他也支持速错(Fail fast)及GenServer编程框架。当然,也有它没有做到的,如:
- CERL 不支持轻量级的进程。尽管CERL的Process模型和Erlang一致,但是他并不轻量,事实上CERL的进程就是操作系统中的Thread。刚开始我准备用协程,不过后来被证明这是不必要的,所以CERL永远都不考虑协程这东西。
- CERL 不支持热部署。当然,这是指他没有提供机制支持,而不是指你没法做到。
为什么是CERL,而不用Erlang?
刚开始我确实用Erlang,而且完成了DEMO。不过,我最终发现这中间存在些问题。尽管Erlang的思想是非常优秀的,但是:
- Erlang是小众语言,使用它存在维护上的风险。(这个是缺点,但不是我最重要的理由)
- Erlang的变量不可变,改变了程序员的思维习惯。
- 我个人原则上认可变量应不可变带来的好处,但是允许特殊情形下的可变(例如for循环的自变量)。
- Erlang是动态语言。
- 我个人认为大型工程都应该使用静态类型语言,以获得更好的代码质量和性能(静态类型语言更容易写出高质量少BUG的代码)。
其实,最终极的理由是第3条。
CERL简介
CERL对外表现为一个动态库(cerl.dll) + 一个编译器(sdlc)。逻辑上CERL分为两层,底层称为CERL Core,上层为GenServer框架。
- CERL Core –表现为动态库cerl.dll。主要实现Erlang的分布式模型,及其他辅助基础设施。
- GenServer框架。它主要定义了服务器接口描述语言: Server Description Language(SDL),并提供相应的编译器 sdlc 以生成:
- 客户端如何调用该服务器的Proxy代码。
- 服务器端Stub代码。
- 服务器Impl文件的框架代码。
- 如果没有erlang中SIP。那么erlang就失去了它大部分特色了。基于角色和轻量级进程是erlang作为并发语言的支柱。分布式只是软件独立进程的附加好处。如何利用多核CPU才是erlang的精髓所在,分布式也可以看做是多CPU利用的一种方式。所以感觉你实现的这个东西,怎么说了,有点“买椟还珠”的意思了。
- @zeusever: 性能是CERL最重要的目标。不支持协程其实也是出于性能的考虑。基于线程池的代码性能一定可以比基于协程好的(毕竟协程需要有额外的维护代价)。然而线程池是非用户友好的,因为它使得服务器增加了一些零时的状态。如果没有一个好的状态机去包装它,会使得编码变得复杂。
所以,可能CERL为了用户友好,协程可能还是不得不去支持(好在是否支持协程与CERL用户无关,CERL的接口不会发生变化)。除非有一天我想到一个合适的状态机类,协程就可能真的被我干掉了。
Erlang类型及函数声明规格
Author: litaocheng
Mail: litaocheng@gmail.com
Date: 2009.6.8
Copyright: This document has been placed in the public domain.
Contents:
- 概述
- 意义
- 规范
- 类型及其定义语法
- 自定义类型定义
- 在record中使用类型声明
- 函数规范定义
- 使用dialyzer进行静态分析
- 生成plt
- 使用dialyzer分析
- 参考
概述
Erlang为动态语言,变量在运行时动态绑定,这对于我们获取函数的参数及返回值的类型信息具有一定的难度。为了弥补这个不足,在Erlang中我们可以通过type及spec定义数据类型及函数原型。通过这些信息,我们对函数及调用进行静态检测,从而发现一些代码中问题。同时,这些信息也便于他人了解函数接口,也可以用来生成文档。
意义
- 定义各种自定义数据类型
- 定义函数的参数及返回值
- dialyzer 进行代码静态分析
- edoc利用这些信息生成文档
规范
类型及其定义语法
数据类型由一系列Erlang terms组成,其有各种基本数据类型组成(如 integer() , atom() , pid() )。Erlang预定义数据类型代表属于此类型的所有数据,比如 atom() 代表所有的atom类型的数据。
数据类型,由基本数据类型及其他自定义数据类型组成,其范围为对应数据类型的合集。比如:
atom() | 'bar' | integer() | 42
与:
atom() | integer()
具有相同的含义。
各种类型之间具有一定的层级关系,其中最顶层的 any() 可以代表任何Erlang类型,而最底层的 none() 表示空的数据类型。
预定义的类型及语法如下:
Type :: any() %% 最顶层类型,表示任意的Erlang term
| none() %% 最底层类型,不包含任何term
| pid()
| port()
| ref()
| [] %% nil
| Atom
| Binary
| float()
| Fun
| Integer
| List
| Tuple
| Union
| UserDefined %% described in Section 2
Union :: Type1 | Type2
Atom :: atom()
| Erlang_Atom %% 'foo', 'bar', ...
Binary :: binary() %% <<_:_ * 8>>
| <<>>
| <<_:Erlang_Integer>> %% Base size
| <<_:_*Erlang_Integer>> %% Unit size
| <<_:Erlang_Integer, _:_*Erlang_Integer>>
Fun :: fun() %% 任意函数
| fun((...) -> Type) %% 任意arity, 只定义返回类型
| fun(() -> Type)
| fun((TList) -> Type)
Integer :: integer()
| Erlang_Integer %% ..., -1, 0, 1, ... 42 ...
| Erlang_Integer..Erlang_Integer %% 定义一个整数区间
List :: list(Type) %% 格式规范的list (以[]结尾)
| improper_list(Type1, Type2) %% Type1=contents, Type2=termination
| maybe_improper_list(Type1, Type2) %% Type1 and Type2 as above
Tuple :: tuple() %% 表示包含任意元素的tuple
| {}
| {TList}
TList :: Type
| Type, TList
由于 lists 经常使用,我们可以将 list(T) 简写为 [T] ,而 [T, ...] 表示一个非空的元素类型为T的规范列表。两者的区别是 [T] 可能为空,而 [T, ...] 至少包含一个元素。
'_' 可以用来表示任意类型。
请注意, list()表示任意类型的list,其等同于 [_]或[any()], 而 [] ,仅仅表示一个单独的类型即空列表。
为了方便,下面是一个内建类型列表
Built-in type Stands for |
|
term() |
any() |
bool() |
'false' |
byte() |
0..255 |
char() |
0..16#10ffff |
non_neg_integer() |
0.. |
pos_integer() |
1.. |
neg_integer() |
..-1 |
number() |
integer() |
list() |
[any()] |
maybe_improper_list() |
maybe_improper_list(any(), |
maybe_improper_list(T) |
maybe_improper_list(T, |
string() |
[char()] |
nonempty_string() |
[char(),...] |
iolist() |
maybe_improper_list( char() |
module() |
atom() |
mfa() |
{atom(),atom(),byte()} |
node() |
atom() |
timeout() |
'infinity' |
no_return() |
none() |
类型定义不可重名,编译器可以进行检测。(转载注:在R13,如果采用 -type 和 -spec 标注,编译阶段会进行这种检测,然而,因为标注仍然是可选的,所以,如果没有使用标注,则不会进行检测。)
注意 : 还存在一些其他 lists 相关的内建类型,但是因为其名字较长,我们很少使用:
nonempty_maybe_improper_list(Type) :: nonempty_maybe_improper_list(Type,any())
nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any())
我们也可以使用record标记法来表示数据类型:
Record :: #Erlang_Atom{}
| #Erlang_Atom{Fields}
当前R13B中,已经支持record定义中的类型说明
自定义类型定义
通过前一章节的介绍,我们知道基本的类型语法为一个atom紧随一对圆括号。如果我们想第一个一个新类型,需要使用 'type' 关键字:
-type my_type() :: Type.
my_type为我们自定义的type名称,其必须为atom,Type为先前章节介绍的各种类型,其可以为内建类型定义,也可以为可见的(已经定义的)自定义数据类型。否则会编译时保错。
这样递归的类型定义,当前还不支持。
类型定义也可以参数化,我们可以在括号中包含类型,如同Erlang中变量定义,这个参数必须以大写字母开头,一个简单的例子:
-type orddict(Key, Val) :: [{Key, Val}].
在record中使用类型声明
我们可以指定record中字段的类型,语法如下:
-record(rec, {field1 :: Type1, field2, field3 :: Type3}).
如果字段没有指明类型声明,那么默认为 any() . 比如,上面的record定义与此相同:
-record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).
如果我们在定义record的时候,指明了初始值,类型声明必须位于初始值之后:
-record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3})$
我们可以指定record中字段的类型,语法如下::
-record(rec, {field1 :: Type1, field2, field3 :: Type3}).
如果字段没有指明类型声明,那么默认为 any() . 比如,上面的record定义与此相同:
-record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).
如果我们在定义record的时候,指明了初始值,类型声明必须位于初始值之后:
-record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).
如果初始值类型与字段的类型声明不一致,会产生一个编译期错误。 filed的默认值为 'undefined' ,因此下面的来个record定义效果相同:
-record(rec, {f1 = 42 :: integer(),
f2 :: float(),
f3 :: 'a' | 'b').
-record(rec, {f1 = 42 :: integer(),
f2 :: 'undefined' | float(),
f3 :: 'undefined' | 'a' | 'b').
所以,推荐您在定义record时,指明初始值。
record定义后,我们可以作为一个类型来使用,其用法如下:
#rec{}
在使用recored类型时,我们也可以重新指定某个field的类型:
#rec{some_field :: Type}
没有指明的filed,类型与record定义时指明的类型相同。
函数规范定义
函数规范可以通过新引入的关键字 'spec' 来定义(摒弃了旧的 @spec 声明)。其语法如下:
-spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.
函数的参数数目必须与函数规范定义相同,否则编译出错。
在同一个module内部,可以简化为:
-spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.
同时,为了便于我们生成文档,我们可以指明参数的名称:
-spec Function(ArgName1 :: Type1, ..., ArgNameN :: TypeN) -> RT.
函数的spec声明可以重载。通过 ';' 来实现:
-spec foo(pos_integer()) -> pos_integer()
; (integer()) -> integer().
我们可以通过spec指明函数的输入和输出的某些关系:
-spec id(X) -> X.
但是,对于上面的spec,其对输入输出没有任何限定。我们可以对返回值增加一些类似guard的限定:
-spec id(X) -> X when is_subtype(X, tuple()).
其表示X为一个tuple类型。目前仅仅支持 is_subtype 是唯一支持的guard。
某些情况下,有些函数是server的主循环,或者忽略返回值,仅仅抛出某个异常,我们可以使用 no_return() 作为返回值类型:
-spec my_error(term()) -> no_return().
my_error(Err) -> erlang:throw({error, Err}).
使用dialyzer进行静态分析
我们定义了type及spec,我们可以使用 dialyzer 对代码进行静态分析,在运行之前发现很多低级或者隐藏的错误。
生成plt
为了分析我们的app或者module,我们可以生成一个plt文件(Persistent Lookup Table),其目的是为了加速我们的代码分析过程,plt内部很多类型及函数信息。
首先我们生成一个常用的plt文件, 其包含了以下lib:erts, kernel, stdlib, mnesia, crypto, sasl, ERL_TOP为erlang的安装目录,各个lib因为erlang版本不同会有所差别,我当前使用R13B(erl 5.7.1):
dialyzer --build_plt -r $ERL_TOP/lib/erts-5.7.1/ebin \
$ERL_TOP/lib/kernel-2.13.1/ebin \
$ERL_TOP/lib/stdlib-1.16.1/ebin \
$ERL_TOP/lib/mnesia-4.4.9/ebin \
$ERL_TOP/lib/crypto-1.6/ebin \
$ERL_TOP/lib/sasl-2.1.6/ebin
经过十几分钟的的等待,生成了一个~/.dialyzer_plt文件,在生成plt时,可以通过--output_plt 指定生成的plt的名称。
我们也可以随时通过: dialyzer --add_to_plt --plt ~/.dialyzer_plt -c path_to_app 添加应用到既有plt中,也可以通过: dialyzer --remove_from_plt --plt ~/.dialyzer_plt -c path_to_app 从已有plt中删除某个应用。
例子:
% 生成plt
dialyzer --build_plt -r /usr/local/lib/erlang/lib/erts-5.7.1/ebin \
/usr/local/lib/erlang/lib/kernel-2.13.1/ebin \
/usr/local/lib/erlang/lib/stdlib-1.16.1/ebin \
/usr/local/lib/erlang/lib/mnesia-4.4.9/ebin \
/usr/local/lib/erlang/lib/crypto-1.6/ebin \
/usr/local/lib/erlang/lib/sasl-2.1.6/ebin
% 从plt中去处crypto应用
dialyzer --remove_from_plt --plt ~/.dialyzer_plt -c /usr/local/lib/erlang/lib/crypto-1.6/ebin
% 向plt中添加crypto应用
dialyzer --add_to_plt --plt ~/.dialyzer_plt -c /usr/local/lib/erlang/lib/crypto-1.6/ebin
使用dialyzer分析
生成plt后,就可以对我们书写的应用进行静态检查了。
假设我们书写一个简单的module(spec/spec.erl):
-module(spec).
-compile([export_all]).
-vsn('0.1').
-spec index(any(), pos_integer(), [any()]) -> non_neg_integer().
index(Key, N, TupleList) ->
index4(Key, N, TupleList, 0).
index4(_Key, _N, [], _Index) -> 0;
index4(Key, N, [H | _R], Index) when element(N, H) =:= Key -> Index;
index4(Key, N, [_H | R], Index) -> index4(Key, N, R, Index + 1).
% correct:
%-spec fa( non_neg_integer() ) -> pos_integer().
% invalid:
-spec fa( N :: atom() ) -> pos_integer().
fa(0) -> 1;
fa(1) -> 1;
fa(N) -> fa(N-1) + fa(N-2).
-spec some_fun() -> any().
some_fun() ->
L = [{bar, 23}, {foo, 33}],
lists:keydelete(1, foo, L).
编译spec.erl:
erlc +debug_info spec.erl
使用dialyzer进行分析:
dialyzer -r ./spec
显示结果:
Checking whether the PLT /home/litao/.dialyzer_plt is up-to-date... yes
Proceeding with analysis...
spec.erl:15: Invalid type specification for function 'spec':fa/1. The success typing is (non_neg_integer()) -> pos_integer()
spec.erl:22: Function some_fun/0 has no local return
spec.erl:24: The call lists:keydelete(1,'foo',L::[{'bar',23} | {'foo',33},...]) will never return since it differs in argument position 2 from the success typing arguments: (any(),pos_integer(),maybe_improper_list())
done in 0m0.29s
done (warnings were emitted)
我们可以看到,我们的fa/1函数的spec信息错误,我们进行修正:
由
-spec fa( non_neg_integer() ) -> pos_integer().
改为:
-spec fa( N :: atom() ) -> pos_integer().
some_fun中,lists:keydelete/3参数顺序进行修改:
lists:keydelete(1, foo, L).
改为:
lists:keydelete(foo,1, L).
重新编译,进行dialyzer分析,提示成功:
litao@litao:~/erltest$ dialyzer -r ./spec
Checking whether the PLT /home/litao/.dialyzer_plt is up-to-date... yes
Proceeding with analysis... done in 0m0.28s
done (passed successfully)
参考
[1] EEP 8,Types and function specifications (http://www.erlang.org/eeps/eep-0008.html)
[2] reRestructureText (http://docutils.sourceforge.net/docs/user/rst/quickref.html)
[3] dialyzer (http://www.erlang.org/doc/man/dialyzer.html)
“存在就是合理”
从接触erlang到现在已经3个月了,网上也看了不少关于erlang的文章,许多人也说要在中国推广erlang,但是关于应用方面的文章太少了,大多都是erlang原理、源码的研究,我不是否认这种做法,而是感觉这种做法是没法在中国推广erlang的。投资人或老板不会听你说这种语言的代码写的多么优雅、思想多么先进就会支持你的,他们关心的是erlang有多少成功应用。所以想要推广erlang就要务实,多写些应用,多翻译文档。在推广erlang的时候只要说erlang的什么什么应用成功了,erlang在什么什么应用中的并发、分布、健壮、软实时性、热代码升级表现的多么多么好就行了。
注:这只是我个人的观点,请不要投西红柿,谢谢
potian 写道
“非工业主流”编程语言
所以为什么是“非工业主流”编程语言。
我不知道如何下一个严格的定义。简单地说,就是不被绝大多数程序员所使用的编程语言。
看看tiobe的语言排行榜,你可以了解我在讲什么。Java、C、VB、C++和PHP占据了70%的份额,它们是当之无愧的工业主流语言。而 Ruby尽管连连升级,排名13位,份额也不过是0.804%,Lisp/Scheme连连下挫,目前仅为0.586%,如果你仔细寻找,在The Next 50 Programming Languages的标题下,Erlang,Lua,Scala缩在角落里,这些“可怜”的语言当然是非主流的。自然,你不不太可能认为PL/SQL 、 Visual FoxPro、 VB.NET和Lisp、Ruby、Erlang、Lua是同类,我也这样想。
或许你和我曾经或正在感到非常振奋,那些你我日常的编程语言高居前列,并引以为豪。但是事情并不是完全象我们想像的一样。
编写程序需要乐趣,很难说工业主流语言能够提供你更多的乐趣。我所知道的很多程序员在白天忙乎完手上的Java,C++工作后,晚上会带着一种神秘的快感摸索一些可能自己一辈子也不会用于谋生的语言。
当然,这可能是厌倦造成的,但是当你发现一个苦思冥想、或者需要n多语言规则、框架、n多所谓的高深理论解决的问题,在另外一种语言中是最最简单的一个特性,恐怕这种懊恼的感觉不是可以轻易描述的。譬如,当你天天为C/C++的内存释放绞尽脑汁的时候,当你为垃圾收集在Java的出现而欢呼的时候,你是否知道30年前,那已经是Lisp的一个标准构造了。当你天天面对着无穷无尽的并发要求,纠缠不清的哲学家吃面头皮发麻的时候,你可能很想知道Erlang 20年前就让极大规模的并发和可靠性处理变成小事一桩。
编写程序还需要创造价值,一个非凡的产品在获得巨大利润的同时,更会带来一种心底而生的自豪感。如果要担心工作的问题,那么主流语言是你必不可少的谋生工具。但是如果你从头建立一个公司,希望用有限的资源和人力制造出强有力的产品,一个与众不同的产品,那么你需要秘密武器,这些武器是什么呢?
当然可以有很多,但其中最有杀伤力的武器之一无疑是编程语言--高生产力,适合某一领域的非工业主流语言。这种例子并不罕见,例如:
* Beating the Averages
* Making Money from Erlang
* google
也许,你喜爱的语言被成千上万的人使用并不是那么令人自豪的事情;自私一点地说,缺乏同伴或许能够带来更多的乐趣和财富
potian 写道
编写可靠软件-面对不可避免的错误
学习一种新语言的目的是什么?
最重要的是能够从新的语言中吸收到新的营养,从不同的角度去理解我们所碰到的问题,以及新的解决问题方式。至少不至于坐井观天。
或许你认为理想上经过严格测试的软件是不应该有Bug的,而当Bug作恶的时候你会产生罪恶感,你觉得总有那么一条道路可以通向完美的零缺陷之路,你自责、困扰。但你是否曾经反过来想想,这种理想是永远达不到的,但是如果我们承认系统、软件是包含错误的,我们是不是无能为力了?
这两年来我做的一些程序和以往的软件相比有一点很大的不同,那就是经常需要和硬件打交道。我们经常碰到的一个问题是在很多情况下,譬如温度过高、或者运行时间过长、或者磁盘读写、删除次数太多的情况下,DSP会暂时停止运行,或者磁盘会损坏。这个时候,你能干什么?假装这些事情不会发生?
有些时候也并不完全是硬件的问题,在如今高度紧张的竞争环境下,我们的客户需求变更完成时间通常以天或者周为单位,如此的速度去响应你的客户,你能保证你的代码没有任何问题?
没有人能够保证,但是这个时候,你还必须做到这样的系统是能够可靠运行的,就拿我们的其中一个软件来说,虽然本身价值不高,但是它可能牵涉到大量的金钱、甚至是人的生命。
为了解决这些问题,在某些关键的位置,工程人员必须在机器上插入硬件狗,在机器停止响应的时候自动重新启动机器。但是某些时候机器本身是没有问题的,而是系统或者软件的问题,例如DSP因长时间运行停止运作,或者程序本身产生错误,所以我们需要软件狗,一个守护进程,监视系统的运行状态,并在出现异常的情况下重启系统。
然而,这样还是不够的,某些时候,整个系统的运行是正常的,只不过某些模块会出现问题,因此,我们进一步把整个系统划分为几个重要的模块,单独的进程运行这些模块,一个比较完备的守护进程管理这些模块之间的依赖关系、它们的启动、状态监视以及出现异常的情况下按照某种顺序重新启动。
这样的需求和实现出现在我们一个小小的产品中,但很快发展到我们所有的软件中。例如,视频编解码通常需要大量的CPU,而在CPU 非常紧张的情况下或者某些时候编码器网络传输的流数据不正常,解码器通常会出现异常的情况,轻则停止解码,重则退出系统。而在监控大量视频图像的情况下,工作人员或许根本无法注意到哪一路图像出现异常。所以,客户的要求是不管因为网络、编解码软件或者其他什么原因,必须保证24小时永不停止的图像显示(如果能够自动恢复的话),在我们流媒体、集中存储系统中也存在着同样的需求。
因此,我们必须假设系统、程序是包含错误的,在这样的情况下我们如何编写可靠的软件?
我们用自己的守护系统来应对这个问题,但是并不是那么系统、完备和彻底。
而这也是Erlang要解决的问题,通过编程语言、通过库、通过良好的系统原则来构造可靠的强壮的系统,如果我能够早一年看到,或许就会少走许多弯路,能走得更好。
Erlang是Ericsson Language的简称,上世纪80年代由爱立信实验室发展出来的一种编程语言,起源于Prolog,比较经常用于电信级的软件开发,创始人为Armstrong。
Erlang的特点是高并发、高容错、分布式的函数编程语言。非常适合于大规模的电信程序,这些程序往往要求5个9的可用性、要求程序具备在线升级的能力、具备高并发能力,而Erlang完全满足这些要求。并且随着Erlang虚拟机开发的推荐,现在的Erlang语言效率也是极高的。
用Erlang实现与Java相同规模的分布式程序,代码量至少减少70%,这是经过Erlang社区长期实践的结论。并且Erlang具备很好的机制来和其他语言所写的代码交互。
Erlang的成功案例很多,Facebook的聊天功能就是Erlang实现的;爱立信的大规模电信服务也也是由Erlang实现的,代码规模据说已经达到百万级;国内像豆瓣、webQQ都用了Erlang实现部分功能。
Erlang的缺点也比较明显,语法太过简单,表达能力有限。需要使用OTP这样的框架来指导和保护程序。其次和现有代码的紧耦合也不够好。
clojure.org/
Clojure也是我刚刚开始了解的一门语言。它有两个非常好的特性,极度的吸引我:1,它是lisp的一种方言;2,它基于JVM实现,与java无异。
Clojure在保护现有软件资产上简直是做到了极致,与java程序可以无缝的来回调用,而且它本身也非常小,非常小。
从特点上来说,它是一种非常灵活的Lisp方言,继承了Lisp的几乎全部特性,特别是功能强大的宏以及怪异的括号语法。其次它与Erlang一样倾向于使用不可改变的变量来提高并行性,使用STM(软件事务内存)来处理临界区域,具有非常好的并发性能,被称为JVM中的Erlang。
Clojure的第一个版本发布于2007年,到现在已经发展的非常迅速了,包括Twitter在内的很多公司都在使用Clojure进行开发,比如前一段时间刚刚发布的分布式实时处理框架:Storm就是用Clojure实现的。
进了游戏行业,弃Java而用Erlang两个月后,对erlang的一些浅薄认识:
优点:
(1)一切弱类型脚本语言的优点。
(2)现成的组件都很实用,包括ets、进程字典、gen_server、gen_fsm等。
(3)消息型并发模型和不变变量减少了需要考虑并发加锁的场景。
缺点:
(1)一切弱类型脚本语言的缺点,没有静态类型检测,在调用其他模块时比较依赖注解,不然就要自己看代码,影响效率。还有代码修改时往往不能及时发现依赖这段代码的其他代码也要修改,编译器有时候不会告诉你,要到运行时才发现。
(2)调试困难,倒不是没有调试工具,IM还是有的,但是非常不好用,有时候还不如用老方法debug("1111")来得快。
(3)由于函数式编程的语法特点,有些算法实现起来不是太顺手。
(4)还是语言特点,匹配模式和不变变量,让本来可以不那么长的程序结构变长了。
(5)使用record时必须很小心,不然很容易覆盖。
(6)伤眼!!妥妥的!!!
其实,大凡人造的东西,都会有缺陷。Erlang也不例外。新科 Clojure 也不例外。
关键还看,这个工具是用来做啥的。对于Erlang,它是用于高可靠高并发服务器环境的。其所做的大部分取舍是合理的。
虽然,在大部分时候瑞士军刀型的工具可以提供一个中庸的解决方案。但是,用合适的工具做适合的工作,是比高效高质量的解决方案。
这些特性,说它人见人爱真是一点都不过分。
采访Erlang的发明者
Joe Armstrong博士是Erlang的发明者,也是《Erlang程序设计》一书的作者。我和这位来自瑞典斯德哥尔摩的Erlang语言首位实现者的访谈记录如下。
Bruce:你为什么要开发Erlang?
Armstrong博士:纯属巧合。我本来没打算发明一门新的编程语言。当时,我想找一种更好的方式来编写电信交换控制软件。我先试了试Prolog。Prolog是一门绝妙的语言,但它无法完全满足我的需要,既然如此,我就开始瞎倒腾Prolog。我琢磨着:“如果改变一下Prolog的编程方式,那会怎样?”于是,我写了个Prolog的元解释器,给它加上了并行进程,还加上了错误处理机制,诸如此类。就这样,过了一段时间,我给这些新增加的变化起了个名字——Erlang,一门新语言就这么诞生了。之后,越来越多的人加入这个项目,这门语言也逐渐发展起来。我们想出了编译它的方法,加入了更多东西,获得了更多用户……
Bruce:你最喜欢它哪一点呢?
Armstrong博士:我最喜欢它的错误处理、运行时代码升级机制,还有位级(bit-level)模式匹配。错误处理是这门语言最不为人所知的部分,也是与其他语言差别最大的部分。Erlang的“非防御”编程和“就让它崩溃”这一套概念,既是它的独门绝学,也是它与传统方法截然相反之处。不过,这样做的确能编出简洁而漂亮的程序。
Bruce:如果能让时光倒流,你最想改变哪项特性?(换言之,你也可以回答这样一个问题:Erlang最大的局限是什么?)
Armstrong博士:这问题很难,我可能会在不同时间给出不同答案。为这门语言添加一些移动特性应该不错,这样我们就能通过移动通信网络传送计算结果。我们可以用库代码来做这件事,但它并不被语言本身所支持。我现在想,如果追本溯源,把Prolog式的谓词逻辑加入Erlang,产生一种谓词逻辑和消息传递的全新组合,那想必会十分美妙。
还有不少小改动也是我想做的,比如说,加入散列映射、高阶模块,等等。
要是推倒重来,我可能会更多地把心思花在各项编程事务的协调上,比如说,如何运作有大量代码的大型编程项目——如何管理代码版本、如何搜索想要的东西、各种事物如何演化。当程序员编写了大量代码之后,他的任务就不再是编写新代码,而是准确找到现有代码,并把现有代码整合起来。因此,搜索和协调就变得日渐重要。如果把GIT和Mercurial这类系统的思想吸收到Erlang之中,再给它加上类型系统,使它能在可控条件下理解代码是如何演化的,那我想应该会带来不错的效果。
Bruce:在实际产品中,你见过的最特别的Erlang应用是什么?
Armstrong博士:嗯,其实我并不会太过惊讶,因为我早就知道它能达到何等高度。当我把Ubuntu版本升级到Karmic Koala时,我发现,它为了支持正在我机器上运行的CouchDB,而在后台悄悄启动了Erlang。这就好比Erlang在雷达的严密监控之下,偷偷溜进了数千万用户的计算机当中。