Erlang

**Erlang**（/ˈɜːrlæŋ/）是一種通用的平行導向函數式程式語言。Erlang 也可以指 Erlang/OTP 的通稱，開源電信平台（OTP）是 Erlang 的常用執行環境及一系列標準元件。

Erlang 執行環境為專有以下要求的系統設計：

* 分散式運算
* 高故障容許度
* 軟性即時運算
* 高可用性、不停運作的應用
* 熱插拔，可以修改程式而不必停機

Erlang 是運作於虛擬機的直譯式語言，但是現在也包含有烏普薩拉大學高效能 Erlang 計劃（HiPE）開發的原生程式碼編譯器，自 R11B-4 版本開始，Erlang 也支援腳本直譯器。在編程範型上，Erlang 屬於多重典範程式語言，涵蓋函數式、平行式及分散式。循序執行的 Erlang 是一個及早求值，單次賦值和動態資料形態的函數式程式語言。

它由喬・阿姆斯特朗（Joe Armstrong）在瑞典電信裝置製造商愛立信所轄的電腦科學研究室開發，目的是創造一種可以應付大規模並發活動的程式語言和執行環境。Erlang 於 1987 年釋出正式版本，最早是愛立信擁有的私有軟體，經過十年的發展，於 1998 年發表開放原始碼版本。

## 開發及演變歷史

Erlang 得名於丹麥數學家及統計學家 Agner Krarup _Erlang_，同時 Erlang 還可以表示 _Er_icsson _Lang_uage。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 程式的基本構成部份，涵蓋**資料格式**、**表達式格式**與**內建函式**。

### 資料格式

### 表達式格式

### 內建函式

開源電信平台包括一個 Erlang 直譯器、一個 Erlang 編譯器、程式節點通訊協定、CORBA、一個分散式資料庫 Mnesia（頁面存檔備份，存於網際網路檔案館）、以及許多程式庫。**內建函式**涵蓋了各種方面的功能，涵蓋了系統命令、資料存取、格式轉換、網路通訊、圖形介面、 ... 等。以下列表介紹幾項常用的 Erlang 內建函式。(參閱檔案（頁面存檔備份，存於網際網路檔案館）或索引 （頁面存檔備份，存於網際網路檔案館）)

## Hello World 程式

這是輸出 Hello World 的一種方式：

若要編譯這個程式，將它存為一個名為 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([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 最主要的特色是**平行導向程式設計**，強調多程式平行運作，並且以訊息對彼此溝通。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>
>
```

### 容錯處理

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（簡體中文）

## 參考資料

## 延伸閱讀

## 外部連結

* Erlang 開放原始碼版本（頁面存檔備份，存於網際網路檔案館）（英文）
* Erlang 愛立信授權版本（頁面存檔備份，存於網際網路檔案館）（英文）
* 因應軟體錯誤建構可靠的分散式系統（頁面存檔備份，存於網際網路檔案館） （頁面存檔備份，存於網際網路檔案館）（英文）
* 開放式目錄計劃中和 Erlang 相關的內容
* Erlang 教學（頁面存檔備份，存於網際網路檔案館）