Erlang
是一种通用的平行导向编程语言,它由瑞典电信设备制造商爱立信所辖的计算机科学研究室开发,目的是创造一种可以应付大规模开发活动的编程语言和执行环境。Erlang问世于1987年,经过十年的发展,于1998年发表开放源码版本。Erlang是运作于虚拟机的直译式语言,但是现在也包含有乌普萨拉大学高性能Erlang计划(HiPE)[1]开发的原生程式码编译器,自R11B-4版本开始,Erlang也支持脚本直译器。在程式设计典范上,Erlang属于多重典范编程语言,涵盖函数式、平行式及分布式。循序执行的Erlang是一个及早求值, 单次赋值和动态资料形态的函数式编程语言。
开发及演变历史
Erlang得名于丹麦数学家及统计学家Agner Krarup Erlang,同时Erlang还可以表示Ericsson Language。Erlang语言由瑞典爱立信电信公司的Joe Armstrong开始设计,开始于公元一九八零年代。最初是以Prolog编程语言为基础,几度改版之后,改成以Joe’s Abstract Machine为基础的独立语言执行环境。虽然语言风格仍与Prolog相近,不过因Erlang语言设计的走向,Erlang成为具备函数语言特色的编程语言[2]。
发行版本
1998年起,Erlang发布开放源码版本,称为开源电信平台。开源电信平台采用修改过的Mozilla公共许可证协议发放,同时爱立信仍然提供商业版本的技术支持。目前,Erlang最大的商业用户是爱立信,其他知名用户有北电网络、亚玛逊以及T-Mobile等[3]。
语言特色
- 平行导向程式设计 在语言中,可以借由spawn/*函数,将特定的函数设定为独立的行程,之后可以做跨行程通讯。
- 函数式程式设计 由于Erlang早期以Prolog开发制成,受语言特性影响,即成为函数式语言。
- 单次赋值 每个变量只能跟资料绑一次,所以,不像一般编程语言的变量可以多次指定为不同的值。单次赋值的好处是状态单纯,使程式容易阅读。
- 及早求值或严格求值 Erlang基本求值策略为电脑语言中及早求值之特性。而且,可以借由明确使用无参数的λ演算式,将特定函数设定为惰性求值策略。
- 动态资料型态与类型系统 有编译时期的型态检查系统支援。
- 错误先发 在执行时期发生的错误,会由错误位置送出讯息,发生错误的行程立刻停止动作。借由行程链通机制,可以自动传递错误、捕捉错误,使其他行程能够帮助处理错误。
- 程式码热更新 由于Erlang是函数语言,可以撰写特定的程式结构,制作即时更换新版函数的机制。
- 脚本语言 Erlang实作提供了脚本执行方式。
语言构成
Erlang程式结构以函数定义为主。函数是一组将输入分别对应到输出的规则,对应方式遵守数学函数的惯例。此外,Erlang语言由几项构句要素所组成,包括文字(或称原子)、数字、列表、值组、字符、字串、二进制资料、模组、与特定用途的关键字如fun … end, if … end, case … of … end, spawn, !, receive … end等等。以下段落分别列示并举例说明Erlang程式的基本构成部份,涵盖资料格式、表达式格式与内建函数。
函数式程式设计
Erlang支持函数式程式设计的一般特色,特色包括单次赋值、递回定义、λ演算与高阶函数等等。Erlang函数大致写法如下,以整数阶乘模组为例:
-module(fact).
-export([fac/1]).
fac(N) when N > 1 ->
N * fac(N-1);
fac(1) ->
1.
以下是快速排序算法的Erlang实作:
%% quicksort:qsort(List)
%% Sort a list of items
-module(quicksort).
-export([qsort/1]).
qsort([]) -> [];
qsort([Pivot|Rest]) ->
qsort([ X || X <- Rest, X <= Pivot]) ++ [Pivot] ++ qsort([ Y || Y <- Rest, Y > Pivot]).
以下是费氏数列求解函数:
-module(example).
-export([fibo/1]).
fibo(N) when N > 1 ->
fibo(N-1) + fibo(N-2);
fibo(1) ->
1;
fibo(0) ->
0.
> c(example).
{ok,example}
> lists:map(fun(X)->test:fibo(X) end, lists:seq(1,10)).
[1,1,2,3,5,8,13,21,34,55]
函数式程式设计难免以递回计算,而消耗了大量递回堆栈空间。为了克服这个问题,一般使用累积参数与尾端递回等技巧节省递回数目:如以下例子。
-module(test).
-export([fibo_accu/1]).
fibo_accu(N) ->
fibo(N, 0, 1).
fibo(N, C1, C2) when N > 2 ->
fibo(N-1, C2, C1+C2);
fibo(0, _, _) ->
0;
fibo(1, _, _) ->
1;
fibo(_, C1, C2) ->
C1+C2.
> c(example).
{ok,test}
> lists:map(fun(X)->test:fibo_accu(X) end, lists:seq(1,10)).
[1,1,2,3,5,8,13,21,34,55]
函数式程式设计容许使用高阶函数求解。以下例子说明Erlang实做复合函数。 ( f o g ,念作 f after g 。)
'After'(F, G) ->
fun(X) ->
erlang:apply(F,
)])
end.
-
- 请注意after是Erlang关键字。因此,以上函数命名为′After′避开关键字。
> (example:'After'(fun test:show/1, fun test:parse/1))(3.1416).
Real number 3.141600 is met.
ok
平行式程式设计
Erlang最主要的特色是平行导向程式设计,强调多程序平行运作,并且以讯息对彼此沟通。Erlang提供了spawn函数和 ! 、 receive … end 等关键字,可以描述在Erlang/开源电信平台中的如何启动一些程序、并且如何让程序传递讯息。此外,平行导向程式设计的精神还强调程序的容错处理,借由程序发生错误时的讯息传递,使其他程序可以得知错误的发生,使方便于后续处理。以下分别介绍平行导向程式设计的一般程式撰写方式,以及错误处理的使用方式。
容错处理
Erlang容错处理机制,由二个步骤实现:一是将二个程序连接起来,二者之间存在一道通讯管道,可提供错误讯息的传递 ── 在此使用link/1函数;二是将程序回报错误的机制打开 ── 在此使用process_flag/2函数。
-module(example).
-compile(export_all).
hello() ->
Pid = spawn(?MODULE, world, []),
link(Pid),
... .
執行時,以 Pid = spawn(example, hello, []) 啟動程序,此程序將啟動另一個程序,並且與它連接。
- 但以上程式还不会有错误讯息的传递机制,因为回报错误的开关还没有打开。
以上 hello/0 函數前段使用process_flag/2函數,將trap_exit標籤打開,即可開啟程序回報錯誤機制。
hello() ->
process_flag(trap_exit, true),
Pid = spawn(?MODULE, world, []),
link(Pid),
... .
于是,当程序结束时,会送出{‘EXIT’, From, Reason}资料。程序正常结束时,Reason为normal。
另外,spawn函数另外有程序连接版本,spawn_link函数,同时启动并连接到新程序。
分布式程式设计
Erlang提供分布式机制,能在另一台电脑启动一些Erlang程序,并由本机电脑对其他电脑的Erlang程序传递讯息。
- 当启动Erlang环境时,加上一个网络节点名称,就进入分布式Erlang模式。节点可以使用埠号与其他节点通讯。
$> erl -name node_1
$> erl -sname node_1
启动新的网络节点时,Erlang使用epmd (Erlang埠号对应管理系统) 指派埠号,提供节点使用。
当知道一个网络节点名称时,可以在该节点产生新程序。
- 在指定节点RemoteNode启动一个程序,spawn启动参数依序为节点名称、模组名称、函数名称、函数的参数列。
% create a remote process and call the function web:start_server(Port, MaxConnections)
% on machine RemoteNode
RemoteProcess = spawn(RemoteNode, web, start_server, [Port, MaxConnections]),
在遠端節點產生新程序之後,可以使用平行式程式設計的技巧,與遠端程序通訊。
Erlang / 开源电信平台提供的程式库,于分布式程式设计可以使用net_adm、net_kernel、slave、… 等模组,做网络通讯[5]。
其他程式设计典范
惰性求值
Erlang程式员可以使用惰性求值。不过,必须使用λ演算式,才能做到惰性求值。
以下是惰性求值的一例:假設有個剖析器程式如下,由於及早求值特徵,本程式將不會求解。
expr() -> alt(then(factor(), then(literal($+), factor())),
then(factor(), then(literal($-), factor()))).
factor() -> alt(then(term(), then(literal($*), term())),
then(term(), then(literal($/), term()))).
term() -> alt(number(),
xthen(literal($(), thenx(expr(), literal($))))).
此處使用λ演算式及適當使用函數名稱表示,就能進行求值。示例如下。
expr() ->
fun () ->
alt(then(fun factor/0, then(literal($+), fun factor/0)),
then(fun factor/0, then(literal($-), fun factor/0)))
end.
factor() ->
fun () ->
alt(then(fun term/0, then(literal($*), fun term/0)),
then(fun term/0, then(literal($/), fun term/0)))
end.
term() ->
fun () ->
alt(number(),
xthen(literal($(), thenx(expr(), literal($)))))
end.
应用
参考资料
- ^ High Performance Erlang [2008-04-13].
- ^ Coders At Work. Book introduction [2010-08-30].见Coders At Work一书对Joe Armstrong的口述记录。
- ^ Who uses Erlang for product development?. Frequently asked questions about Erlang [2008-04-13]. “The largest user of Erlang is Ericsson. Ericsson use it to write software used in telecommunications systems. Many (dozens) projects have used it, a particularly large one is the extremely scalable AXD301 ATM switch.” FAQ中列出的其他用户包括: Nortel、Deutsche Flugsicherung、T-Mobile等
- ^ http://en.wikipedia.org/wiki/Open_Telecom_Platform
- ^ 参考分布式Erlang, http://www.erlang.org/doc/reference_manual/distributed.html