Erlang

LYME is Erlang-based
Erlang（ŋ）是一种通用的-{zh-hans:并发;zh-hant:平行导向;}-函数式-{zh-hans:程序设计语言;zh-hant:程式语言;}-。Erlang也可以指Erlang/OTP的通称，开源电信平台（OTP）是Erlang的常用执行环境及一系列标准元件。
Erlang 执行环境为专有以下要求的系统设计：- 分散式运算- 高故障容许度- 软性即时运算- 高可用性、不停运作的应用- 热插拔，可以修改程式而不必停机
Erlang是运作于虚拟机的-{zh-hans:解释型语言;zh-hant:直译式语言;}-，但是现在也包含有乌普萨拉大学高性能Erlang计划（HiPE）开发的原生程式码编译器，自R11B-4版本开始，Erlang也支持-{zh-hans:脚本方式执行;zh-hant:脚本直译器;}-。在编程范型上，Erlang属于多重典范程式语言，涵盖函数式、-{zh-hans:并行;zh-hant:平行式;}-及-{zh-hans:分布式;zh-hant:分散式;}-。循序执行的Erlang是一个及早求值, 单次赋值和-{zh-hans:动态类型;zh-hant:动态资料形态;}-的函数式程式语言。
它由乔·阿姆斯特朗（Joe Armstrong）在瑞典电信设备制造商爱立信所辖的电脑科学研究室开发，目的是创造一种可以应付大规模并发活动的-{zh-hans:程序设计语言;zh-hant:程式语言;}-和执行环境。Erlang于1987年释出正式版本，最早是爱立信拥有的私有软体，经过十年的发展，于1998年发表开放源码版本。

**开发及演变历史**
Erlang得名于丹麦数学家及统计学家Agner Krarup Erlang，同时Erlang还可以表示Ericsson Language。Erlang语言由瑞典爱立信电信公司的乔·阿姆斯特朗开始设计，开始于公元一九八零年代。最初是以Prolog-{zh-hans:程序设计语言; zh-hant:程式语言;}-为基础，几度改版之后，改成以Joe's Abstract Machine为基础的独立语言执行环境。虽然语言风格仍与Prolog相近，不过因Erlang语言设计的走向，Erlang成为具备函数语言特色的-{zh-hans:程序设计语言; zh-hant:程式语言;}-。

**发行版本**
1998年起，Erlang发布开放源码版本，称为开源电信平台。开源电信平台采用修改过的Mozilla公共许可证协议发放，同时爱立信仍然提供商业版本的技术支持。目前，Erlang最大的商业用户是爱立信，其他知名用户有北电网路、-{zh-hans:亚马逊; zh-hant:亚玛逊}-以及T-Mobile等。

**语言特色**
 -{zh-hans:并行程序设计;zh-hant:平行导向程式设计;}- 在语言中，可以借由spawn/*函数，将特定的函数设定为独立的-{zh-hans:进程; zh-hant:行程;}-，之后可以做跨-{zh-hans:进程;zh-hant:行程;}-通讯。- 函数式-{zh-hans:程序;zh-hant:程式;}-设计 由于Erlang早期以Prolog开发制成，受语言特性影响，即成为函数式语言。- 单次赋值 每个-{zh-hans:变量; zh-hant:变数}-只能跟-{zh-hans:数据;zh-hant:资料;}-绑一次，所以，不像一般-{zh-hans:程序设计语言;zh-hant:程式语言;}-的-{zh-hans:变量;zh-hant:变数;}-可以多次指定为不同的值。单次赋值的好处是状态单纯，使-{zh-hans:程序;zh-hant:程式;}-容易阅读。- 及早求值或严格求值 Erlang基本求值策略为电脑语言中及早求值之特性。而且，可以借由明确使用无参数的-{zh-hans:λ表达式;zh-hant:λ演算式;}-，将特定函数设定为惰性求值策略。- -{zh-hans:动态数据类型;zh-hant:动态资料型态;}-与类型系统 有编译时期的-{zh-hans:类型;zh-hant:型态;}-检查系统-{zh-hans:支持;zh-hant:支援;}-。- -{zh-hans:快速失败;zh-hant:错误先发;}- 在执行时期发生的错误，会由错误位置送出讯息，发生错误的-{zh-hans:进程;zh-hant:行程;}-立刻停止-{zh-hans:执行;zh-hant:动作;}-。借由-{zh-hans:进程通讯机制;zh-hant:行程链通机制;}-，可以自动传递错误、捕捉错误，使其他-{zh-hans:进程;zh-hant:行程;}-能够帮助处理错误。- -{zh-hans:代码;zh-hant:程式码;}-热更新 由于Erlang是函数语言，可以撰写特定的-{zh-hans:程序结构;zh-hant:程式结构;}-，制作即时更换新版函数的机制。- 脚本语言 Erlang实作提供了脚本执行方式。

**语言构成**
Erlang-{zh-hans:程序;zh-hant:程式;}-结构以函数定义为主。函数是一组将输入分别对应到输出的规则，对应方式遵守数学函数的惯例。此外，Erlang语言由几项构句要素所组成，包括文字(或称原子)、数字、列表、值组、字元、字串、二进位资料、模组、与特定用途的关键字如fun ... end, if ... end, case ... of ... end, spawn, !, receive ... end等等。以下段落分别列示并举例说明Erlang程式的基本构成部份，涵盖资料格式、表达式格式与内建函数。

**资料格式**

**表达式格式**
 真值运算：

真值比较的结果，如果成功则传回true原子，失败则传回false原子。
请记得，Erlang是以true和false表示布林资料类型。
(略)
运算子
Erlang提供常用的运算子方便基本运算。运算子是用在中序的表达式里，包含 + - * / div(商) rem(余) 等。位元算算有 bnot, band, bor, bxor, bsl(算术左移), bsr(算术右移) 等。用于列表有 ++(列表衔接) --(列表剔除) 等。各种运算式皆可用 ( ) 调整运算优先顺序。
(略)
防卫式date=20110412054042 
防卫式是接在when关键字之后的一组表达式，借由防卫式的真伪值做程式控制处理。
防卫式的原则如下方所述：
# 代表true或false的变数或原子，是防卫式。
# 任何真值运算式，包括比较算式和逻辑算式，是防卫式。
# 传回true或false的函数呼叫，是防卫式。
# 以逗号分隔的多个防卫式，是防卫式。- false- A = 表达式 , 表达式 , ...
若一条函数规则加上防卫式，此规则的处理范围会多一些限制。受防卫式限制的函数对应规则格式为：
原子 ( 变数 , 变数 , ... ) when 防卫式 -> 表达式 , 表达式 , ...- atom_pair(A, B) when is_atom(A), is_atom(B) -> {A, B}.
行后注解
任何 % 符号开头，往后到行尾的文字皆为注解文字。
'H.W.'. % Hello, World!
λ演算式
λ演算式是匿名函数，在Erlang以 fun ... end 关键字叙述。格式为：
fun ( 变数 , 变数 , ... ) -> 表达式 , 表达式 , ... end
使用无参数的λ演算式，可以做出惰性求值的效果。- (fun(X)->X>0 end)(1).
% (λ x . x > 0) 1- lists:takewhile(
fun(X)->
X>0 and X= 表达式, 表达式, ... ;
防卫式 -> 表达式, 表达式, ... ;
......
防卫式 -> 表达式, 表达式, ...
end- parse(A) ->
     if
:        is_number(A),
:        round(A) == A,
:        A >= 0 ->
::           {natural, A};
:        is_number(A) ->
::           {real, A};
:        is_atom(A) ->
::           {word, A};
:        true ->
::           {unknown, A}
     end.
案例式
使用 case ... of ... end 关键字，根据一个变数的案例，带往相对的处理程序。格式为：
case 表达式 of
样式 -> 表达式, 表达式, ... ;
样式 -> 表达式, 表达式, ... ;
......
样式 -> 表达式, 表达式, ...
end- 样式之后可接when防卫式。- show(A) ->
     case A of
:        {natural, N} ->
::           io:format("Natural number ~.10B is met.~n", N);
:        {real, R} ->
::           io:format("Real number ~f is met.~n", R);
:        {word, W} ->
::           io:format("\"~w\" is met.~n", W);
:        {unknown, U} ->
::           io:format("Unknown structure ~w.~n", U)
     end.
试误
使用 try ... catch ... end 关键字叙述试误的情况与结果。格式为：
try 表达式 of
样式 -> 表达式, 表达式, ... ;
样式 -> 表达式, 表达式, ... ;
......
样式 -> 表达式, 表达式, ...
catch
样式(例外) -> 表达式, 表达式, ... ;
样式(例外) -> 表达式, 表达式, ... ;
......
样式(例外) -> 表达式, 表达式, ...
after
表达式, 表达式, ...
end- 不需要使用after段落时，可省略after段落。- 样式之后可接when防卫式。
(略)
接收讯息
每个Erlang程式执行时，都可以从自己程序的邮箱中取得由其他程序送到的讯息。可以使用 receive ... end 关键字接收讯息，格式为：
receive
样式 -> 表达式, 表达式, ... ;
样式 -> 表达式, 表达式, ... ;
......
样式 -> 表达式, 表达式, ...
end- 样式之后可接when防卫式。- loop(FromPid, Result) ->
     receive
:        {FromPid, stop} ->
::           Result;
:        {FromPid, Any} ->
::           loop(FromPid, Result)
     end.
发送讯息
Erlang容许向程序传送讯息。使用 ! 关键字，格式为：
程序代号 ! 讯息- 讯息可以是各种资料格式。讯息资料格式可以是用各种表达式求出的值。
- Pid = erlang:spawn(
     fun() ->
:        receive
::           X -> X
:        end
     end)- 以上产生一个程序。- Pid ! {hello, world}- 以上对Pid送出讯息。
列表解析date=20100323163714 
列表解析，是提供快速建立列表的语法。语法等同于 集合建构式date=20100702150440 。格式为：-  变数(表达式中的元素) 
变数(表达式中的元素) 
-module(hello).
-export(hello_world/0).
hello_world() -> io:fwrite("hello, world\n").
若要编译这个程式，将它存为一个名为 hello.erl 的文字档，然后从 Erlang终端 进行编译。不要忘了在每个命令的最后加上一个句号（.）。例如：
Erlang (BEAM) emulator version 4.9.1 source
Eshell V4.9.1  (abort with ^G)
1> c(hello).
{ok,hello}
（在 Unix系统 上，你可以通过在命令列里输入 "erl" 来进入 Erlang终端。在 Windows系统 上，你需要打开一个 命令提示符 视窗，然后输入 "werl"来进入 Erlang终端，或者在程式功能表中找到 Erlang 的图示。）从 Erlang终端 上运行这个程式：
2> hello:hello_world().
hello, world
ok

**函数式程式设计**
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(Rest) ->
qsort( X  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)->example: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, erlang:apply(G, X))
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/开源电信平台中的如何启动一些程序、并且如何让程序传递讯息。此外，平行导向程式设计的精神还强调程序的容错处理，借由程序发生错误时的讯息传递，使其他程序可以得知错误的发生，使方便于后续处理。以下分别介绍平行导向程式设计的一般程式撰写方式，以及错误处理的使用方式。

**平行导向程式设计**
基本的平行程式示范如下：- 以下启动一个程序。
% create process and call the function web:start_server(Port, MaxConnections)
ServerProcess = spawn(web, start_server, Port, MaxConnections),- 以下是在任何程式中，对先前起动的程序送一则讯息 {pause, 10} 。
% send the {pause, 10} message (a tuple with an atom "pause" and a number "10")
% to ServerProcess (asynchronously)
ServerProcess ! {pause, 10},
- 以下是一段接收讯息的程式。每个程序都拥有一份邮箱，可伫留收到的讯息； receive ... end 程式片断是从程序的邮箱中取出最早伫留的讯息。
% receive messages sent to this process
receive       
a_message -> do_something; 
{data, DataContent} -> handle(DataContent);
{hello, Text} -> io:format("Got hello message: ~s", Text);
{goodbye, Text} -> io:format("Got goodbye message: ~s", Text)
end.

收到 a_message 结果就是 do_something ；收到 {data, DataContent} 结果会呼叫 handle(DataContent) ；
收到 {hello, Text} 结果教是印出 "Got hello message: ..." ，收到 {goodbye, Text} 结果是印出
"Got goodbye message: ..." 。
以下程式，示范产生一组环状传递讯息的程序。
ring_proc(Funs) ->
Ns = lists:seq(1, length(Funs)),
Pids =  spawn(?MODULE, lists:nth(Nth,Funs),) 
Nth 
receive
	ToPid ->
	    func_msg_(ToPid)
end.

func_msg_(ToPid) ->
receive
	stop ->
	    io:format("Stop process ~w~n", self()),
	    ToPid ! stop;
	Message ->
	    io:format("~w: transmit message to ~w~n", self(), ToPid),
	    ToPid ! Message,
	    func_msg_(ToPid)
end.

接收stop讯息，就对下一个程序送stop讯息；接收到其他任何讯息，就对下一个程序送同样的讯息。
如果传送任何其他讯息，就会让所有的程序不断对下一个程序传递讯息。而以下是测试传送stop讯息的执行结果。
> _ = example:ring_proc(func,func,func).
,,
> P ! stop.
Stop process 
stop
Stop process 
> Stop process 
>

**容错处理**
Erlang容错处理机制，由二个步骤实现：一是将二个程序连接起来，二者之间存在一道通讯管道，可提供错误讯息的传递 ── 在此使用link/1函数；二是将程序回报错误的机制打开 ── 在此使用process_flag/2函数。- 使用link(Pid)让程序连接到另一个程序。
-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、... 等模组，做网路通讯。

**其他程式设计典范**

**惰性求值**
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.

**应用**
 Wings 3D，一个用Erlang编写的三维计算机图形软体。-  YAWSdate=20100826033126 ，以Erlang编写的高效HTTP伺服器。-  DISCOdate=20100916105505 ，以Erlang编写的MapReduce架构系统。-  Apache CouchDBdate=20110220214955 ，以Erlang编写的MapReduce文件式资料库系统。-  RabbitMQdate=20110312041529 ，能搭配Erlang运作的讯息伫列系统。- 开放电信平台
*WhatsApp：其后端服务器应用使用了Erlang及FreeBSD。支持了4.5亿的活跃用户-  ejabberddate=20140331224754 ,世界上最流行的XMPP即时通讯服务器-  EMQXdate=20200529115641 ，以Erlang编写的高可用、分布式MQTT消息服务器。

**社区**
 Erlang Central- Erlang Resources 豆瓣小站date=20150504114949 - Erlang中文社区 erlang-china.org- Erlang中文教程 erlang-cn.comdate=20141014214421 - Erlang中文社区 cnerlang.com- Erlang中文 erlang-cn.org

**参考资料**

**延伸阅读**
 dead-url=no - pages= 6–1-  Early history of Erlangdate=20190829000127  by Bjarne Däcker
*pages=152–163

**外部连结**
 Erlang开放源码版本date=20060613061633 - Erlang爱立信授权版本date=20080415124930 - 因应软体错误建构可靠的分散式系统date=20080516051720  date=20080516051720 
*Erlang- Erlang教学date=20191116001849