Erlang

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程式的基本構成部份，涵蓋資料格式、表達式格式與內建函數。

資料格式

表達式格式 

 真值運算：
 :{|
 |-
 | 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, [Any|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內建函數。(參閱文件或索引 )

Hello World 程式 
這是輸出 Hello World 的一種方式：
-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([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

參考資料

延伸阅读

 
 
 Early history of Erlang by Bjarne Däcker

外部連結 
Erlang開放源碼版本
Erlang愛立信授權版本
因應軟體錯誤建構可靠的分散式系統 

Erlang教學

程序设计语言
并发编程语言
函数式编程语言
宣告式編程語言
模式匹配编程语言
Erlang
愛立信
1986年建立的程式語言