热门问题
时间线
聊天
视角

Erlang

来自维基百科,自由的百科全书

Erlang
Remove ads

Erlang/ˈɜːrlæŋ/)是一种通用的并发函数式程序设计语言。Erlang也可以指Erlang/OTP的通称,开源电信平台(OTP)是Erlang的常用执行环境及一系列标准组件。

事实速览 编程范型, 设计者 ...
Remove ads
Thumb
LYME is Erlang-based

Erlang 执行环境为专有以下要求的系统设计:

Erlang是运作于虚拟机解释型语言,但是现在也包含有乌普萨拉大学高性能Erlang计划(HiPE)[3]开发的原生代码编译器,自R11B-4版本开始,Erlang也支持脚本方式执行。在编程范型上,Erlang属于多重典范编程语言,涵盖函数式并行分布式。循序执行的Erlang是一个及早求值, 单次赋值动态类型函数编程语言

它由乔·阿姆斯特朗(Joe Armstrong)在瑞典电信设备制造商爱立信所辖的计算机科学研究室开发,目的是创造一种可以应付大规模并发活动的程序设计语言执行环境。Erlang于1987年发布正式版本,最早是爱立信拥有的私有软件,经过十年的发展,于1998年发表开放源码版本。

Remove ads

开发及演变历史

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

发行版本

1998年起,Erlang发布开放源码版本,称为开源电信平台开源电信平台采用修改过的Mozilla公共许可证协议发放,同时爱立信仍然提供商业版本的技术支持。目前,Erlang最大的商业用户是爱立信,其他知名用户有北电网络亚马逊以及T-Mobile[5]

语言特色

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

语言构成

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

资料格式

更多信息 类型, 意义与构词规则 ...
Remove ads

表达式格式

更多信息 类型, 构词规则 ...
Remove ads

内置函数

开源电信平台包括一个Erlang解释器、一个Erlang编译器、程序节点通信协议、CORBA、一个分布式数据库Mnesia页面存档备份,存于互联网档案馆)、以及许多程序库[6]内置函数涵盖了各种方面的功能,涵盖了系统命令、资料访问、格式转换、网络通信、图形接口、 ... 等。以下列表介绍几项常用的Erlang内置函数。(参阅文件页面存档备份,存于互联网档案馆)或索引页面存档备份,存于互联网档案馆))

更多信息 模块:函数名称 / 参数数目, 用途 ...
Remove ads

Hello World 程序

这是输出 Hello World 的一种方式:[7]

-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
Remove ads

函数式编程

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)->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最主要的特色是面向并发程序设计,强调多程序平行运作,并且以消息对彼此沟通[8]。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)),
    [P|Pids] = [ spawn(?MODULE, lists:nth(Nth,Funs),[]) || Nth <- Ns ],
    [ Pid ! ToPid || {Pid, ToPid} <- lists:zip([P|Pids], Pids++[P]) ]. 

func() ->
    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消息的执行结果。

> [P|_] = example:ring_proc([func,func,func]).
[<0.233.0>,<0.234.0>,<0.232.0>]
> P ! stop.
Stop process <0.233.0>
stop
Stop process <0.234.0>
> Stop process <0.232.0>
>
Remove ads

容错处理

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}资料。程序正常结束时,Reasonnormal

另外,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_admnet_kernelslave、... 等模块,做网络通信[9]

其他程序设计典范

惰性求值

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.

应用


社区

参考资料

延伸阅读

外部链接

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads