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