Erlang

Erlang（ˈ）是一种通用的并发函数式程序设计语言。Erlang也可以指Erlang/OTP的通称，开源电信平台（OTP）是Erlang的常用执行环境及一系列标准元件。

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

- 分散式运算
- 高故障容许度
- 软性即时运算
- 高可用性、不停运作的应用
- 热插拔，可以修改程式而不必停机

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

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

## 开发及演变历史

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

### 发行版本

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

### 语言特色

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

## 语言构成

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

### 资料格式

类型｜意义与构词规则｜例子

原子｜原子是基本资料单元，以一般文字构成。构词规则有： # 以小写英文字元开头、不包含空白的连续文字。# 以单引号包含的任意连续文字。｜、hello、'Hello, World!'、true、a3b

数字｜数字是基本资料单元，可以是整数或实数。# 连续数字符号。# 包含一个小数点的连续数字符号，并不以小数点开头也不以小数点结尾。# 符合前二项原则，并以 + 或 - 符号开头。# 以`#`分割的数字，前者将表示进制。｜、302、3.1416、+1、-2、`16#10`

列表｜列表是与链接序列相同的资料结构。任一列表大致区分为头部与尾部，头部是列表的第一项，尾部是列表除第一项之外的其他部分。# 左边以、右边以 符号，包含一串以逗号分隔的零或多项构句要素。# 符合前项原则，当存在任一 | 符号时， | 的左边有一串逗号分隔的构句要素， | 的右边只有一个构句要素。｜* * 1,2,3、1,2|

值组｜值组是将二个、三个或多个资料放在一起的资料结构。、左边以 {、右边以 } 符号，包含一串以逗号分隔的零或多项构句要素。｜、{}、1},2}

字元｜Erlang将字元存为32位元的整数。# 任何可见的字元，以 $ 开头、后接该字元符号，即表示字元本身。# 任何不可见的字元，可使用以 $ 开头、后接该字元符号的转义序列表达。｜、$3、$)、$\012、$\x0A、$\n

字串｜Erlang将字串视同一列整数列表。# 以双引号包含任意多个文字，即为字串。# 以一列整数列表表达，使其中每个整数项目都落在合理的字元的值范围，此列也是字串。｜、"Hello, World!"、65,66,67

二进制资料｜以左边 > 符号，包含由 位元语法表示的资料。｜、>、>

函数识别项｜Erlang容许用文字表示函数识别项，使程式中可以对指定函数做函数呼叫，或者当做资料传递。函数识别项格式为：、fun 函数名称/参数数目｜、fun a_function/3 用途见以下“函数式程式设计”小节。

程序代号｜Erlang容许以内建函数erlang:spawn/3、erlang:spawn/4、erlang:spawn/1、erlang:spawn/2等等，将指定函数启动为一个程序。程序启动之后，Erlang以左边，包含一个数字和点号组成的编号，表示此程序代号。｜见以下“平行式程式设计”小节。

模组｜Erlang容许将一些程式整理为一个模组。模组的设定，是在源码档案开头书写模组标记，格式为： -module(模组名称). -export( 函数名称/参数数目 , 函数名称/参数数目 , ... ). -import( 模组名称, 函数名称/参数数目 , 函数名称/参数数目 , ... ). 模组名称和函数名称都是原子。-module(模组名称) 定义模组的名字，要与档名相同。-export( ... ) 定义模组释出的函数，模组内的任何函数必须要释出才能让外部透过模组呼叫该函数。-import( ... ) 定义本模组要从其他模组汇入哪些函数，以便本模组自己使用。另外，为了方便程式的撰写并测试，还容许 -compile(export_all) 定义本模组的所有函数全部对外释出。-compile(export_all).｜(略)

宏｜宏是将一项资料以另一个文字做为代名。、定义宏的语法是： -define ( 代名 , 资料 ).、使用宏的语法是： ? 代名、Erlang有一些内定语法，例如模组名称为 ?MODULE｜、-define(hello, world). : test -> ?hello.

### 表达式格式

类型｜构词规则｜例子

变数｜变数是一种提供与资料绑定、赋值的词汇。Erlang的变数是单一赋值，一个变数只能赋值一次。# 以大写英文字元开头的任意连续文字，是具名变数。# 以 _ 开头的任意连续文字，是匿名变数，用于变数必须使用、但相对的值可以忽略的场合。｜、Number1、_、_nothing

样式匹配｜# 样式是指以原子、列表或值组表达的结构，结构中可能包含一些未赋值的变数。# 给二个样式 A 和 B，样式匹配是用 A = B 表示法，表示要让 A 对 B 匹配。如果匹配成功， A 包含的未赋值变数都会赋值，并且传回 B 的值。｜、A = 42、{ok, Node} = {ok, 'Wikipedia'}、T = 1,2,3

函数｜函数是由一或多项对应规则组成。每一项规则是将一部分匹配样式的输入映射到相对的输出。、规则：格式为 原子 ( 变数 , 变数 , ... ) -> 表达式 , 表达式 , ... 在 -> 左边是函数名称及搭配的参数列，右边为函数本体。、函数：格式为 规则 ; 规则 ; ... ; 规则 . 以分号分隔一或多项规则，并最后以句号结束。同一函数的每一规则必须以相同的原子开头，并接受相同数量的 参数列。函数被呼叫时，会让呼叫方依序对被呼叫方的每一条函数规则做样式匹配，比对函数名称、参数数目、参数样式等等。首先完成匹配的函数规则会被执行，并且后面的函数规则会被忽略。｜见以下“函数式程式设计”小节

函数呼叫｜格式为 原子 ( 资料 , 资料 , ... ) 表示函数名称及搭配的参数列。呼叫符合函数名称及 相同参数数目的函数。函数呼叫时，所给予的参数可能是已赋值的变数。并且，如果参数是变数，必须是已赋值的变数。｜见以下“函数式程式设计”小节

真值比较｜、比较运算： :

`==`｜相等 /=｜不相等 ==｜大于或等于 >｜大于 =:=｜确实相等 =/=｜确实不相等、真值运算： : not｜非 and｜且 or｜或 xor｜非此即彼 orelse｜或 (捷径求值) andalso｜且 (捷径求值) 真值比较的结果，如果成功则传回true原子，失败则传回false原子。请记得，Erlang是以true和false表示布林资料类型。｜(略)

运算子｜Erlang提供常用的运算子方便基本运算。运算子是用在中序的表达式里，包含 + -、/ div(商) rem(余) 等。位元算算有 bnot, band, bor, bxor, bsl(算术左移), bsr(算术右移) 等。用于列表有 ++(列表衔接) --(列表剔除) 等。各种运算式皆可用 调整运算优先级。｜(略)

防卫式｜防卫式是接在when关键字之后的一组表达式，借由防卫式的真伪值做程控处理。防卫式的原则如下方所述： # 代表true或false的变数或原子，是防卫式。# 任何真值运算式，包括比较算式和逻辑算式，是防卫式。# 传回true或false的函数呼叫，是防卫式。# 以逗号分隔的多个防卫式，是防卫式。｜、false、A =< 10、erlang:is_number(N), erlang:is_atom(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=<10 end, :1,5,11).

因果式｜使用 if ... end 关键字叙述条件判断原则。格式为： if 防卫式 -> 表达式, 表达式, ... ; 防卫式 -> 表达式, 表达式, ... ; ...... 防卫式 -> 表达式, 表达式, ... 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送出讯息。

列表解析｜列表解析，是提供快速建立列表的语法。语法等同于集合建构式。格式为：、变数(表达式中的元素)｜变数(表达式中的元素) <- 表达式 , 防卫式 : 若无防卫条件，列表解析中不写防卫式。｜、X｜X <- 1,2,3 : 运算结果为1,2,3

### 内建函数

开源电信平台包括一个Erlang直译器、一个Erlang编译器、程序节点通讯协定、CORBA、一个分散式数据库Mnesia、以及许多程式库。

内建函数涵盖了各种方面的功能，涵盖了系统命令、资料存取、格式转换、网络通讯、图形界面、 ... 等。以下列表介绍几项常用的Erlang内建函数。(参阅 文件或 索引 )

模组:函数名称 / 参数数目｜用途

c:cd / 1｜切换到指定目录位置。> c:cd("D:\\code"). D:/code/ ok 当指定目录不正确时，则保持在原目录位置。

c:c / 1｜编译指定的程式码，之后载入新编译好的程式。> c:c(test). % test.erl 必须存在于目录位置 {ok, test} > c:c(test1). ./test1.erl:none: ... error

io:format / 2｜按照指定的格式文字将资料印在标准输出埠。> io:format("~.8B, ~c, ~s, ~.2f~n", 32, $a, "hello", 3.1416). 40, a, hello, 3.14 ok

lists:sublist / 3｜由列表中撷取子列表。Erlang字串是整数列表，于是本函数视同撷取子字串。> lists:sublist("Hello, World!", 2, 2). "el"

## Hello World 程式

这是输出 Hello World 的一种方式：

```erlang
-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 <- Ns ,

Pid ! ToPid

{Pid, ToPid} <- lists:zip(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讯息的执行结果。

> _ = 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编写的三维计算机图形软件。
- YAWS，以Erlang编写的高效HTTP服务器。
- DISCO，以Erlang编写的MapReduce架构系统。
- Apache CouchDB，以Erlang编写的MapReduce文件式数据库系统。
- RabbitMQ，能搭配Erlang运作的讯息伫列系统。
- 开放电信平台
- WhatsApp：其后端服务器应用使用了Erlang及FreeBSD。支持了4.5亿的活跃用户
- ejabberd,世界上最流行的XMPP即时通讯服务器
- EMQX，以Erlang编写的高可用、分布式MQTT消息服务器。

## 社区

- Erlang Central
- Erlang Resources 豆瓣小站
- Erlang中文社区 erlang-china.org
- Erlang中文教程 erlang-cn.com
- Erlang中文社区 cnerlang.com
- Erlang中文 erlang-cn.org

## 延伸阅读

- Early history of Erlang by Bjarne Däcker

## 基本资料

- 编程范型：多范式：函数式、并发
- 发行时间：1986年
- 设计者：乔·阿姆斯特朗、Robert Virding、Mike Williams
- 开发者：爱立信
- 类型系统：动态、强
- 受影响于：Prolog, Smalltalk, PLEX, LISP
- 施影响于：Akka, Clojure, Dart, Elixir, F♯, Opa, Oz, Reia, Rust, Scala
- 文件扩展名：.erl .hrl
- 许可证：Apache许可证2.0（从OTP 18.0开始）、Erlang公共许可证（早期版本）
- wikibooks：Erlang Programming