基礎
Erlang 是一種多用途編程語言,主要用於開發並發和分布式系統。它最初是一種專有的編程語言,Ericsson 使用它來開發電話和通信應用程序。Erlang 在 1998 年開放了其源代碼,之后,由於一些倍受矚目的項目(比如 Facebook 聊天系統)和創新的開放源碼項目(比如 CouchDB 的面向文檔數據庫管理系統)使用了 Erlang,Erlang 在近幾年越來越流行了。在本文中,將了解 Erlang 的概況,並將它的函數編程風格與其他編程模型(比如命令式、過程式和面向對象編程)進行對比。首先,學習如何創建第一個程序(Fibonacci 遞歸函數)。然后,了解 Erlang 語言的基礎知識,對於習慣使用 C、C++、Java™ 和 Python 的開發人員,一開始可能有點兒困難。
什么是 Erlang?
Erlang 由 Ericsson 開發,用於幫助開發管理許多電信項目的軟件。Erlang 的第一個版本發布於 1986 年,1998 年發布了它的第一個開放源碼版本。可以從擴展的 Erlang 版本信息中了解到,Open Telecom Platform (OTP) 是適用於 Erlang 的應用程序開發平台,也是交付 Erlang 開發環境的主要方法。
Erlang 提供許多在其他語言中不存在或難以管理的標准特性。Erlang 中之所以存在這些功能,是因為它最初用於電信領域。
例如,Erlang 包含一個非常簡單的並發模型,允許在同一主機上相對輕松地多次執行代碼塊。除了並發之外,Erlang 還使用一個錯誤模型,允許識別和處理這些進程中的錯誤(甚至可以用新進程處理),因此可以非常輕松地構建容錯能力很強的應用程序。最后,Erlang 包含內置的分布式處理,允許在一台計算機上運行組件的同時從另一台計算機請求它們。
總之,Erlang 為構建分布式、可伸縮和高性能的離散應用程序提供了良好的環境,我們常常使用這種應用程序支持現代網絡和基於 web 的應用程序。
函數編程與其他范例
Erlang 與其他流行的語言之間的主要差異是,Erlang 基本上是一種函數編程語言。函數編程與語言是否支持函數無關,而是指程序操作和組件的工作方式。
在函數編程中,按照與數學計算相似的方式設計語言的函數和操作,語言通過函數執行操作,函數接收輸入並生成結果。函數編程范例 (paradigm) 意味着對於相同的輸入值,代碼塊會產生相同的輸出值。因此,預測函數或程序的輸出容易得多,更容易調試和分析。
與之相對的編程范例是命令式編程語言,比如 Perl 或 Java,這類語言依賴於在執行期間應用程序狀態的改變。在命令式編程語言中,狀態的改變意味着:對於相同的輸入值,程序的組件可以根據程序當時的狀態而產生不同的結果。
函數編程方式很容易理解,但是如果您習慣了過程式和關注狀態的命令式語言,可能不太容易適應它。
獲得 Erlang
可以從 Erlang 網站直接獲得 Erlang(參見 參考資料)。許多 Linux 發行版的存儲庫中也包含它。例如,要想在 Gentoo 上安裝它,可以使用 $ emerge dev-lang/erlang
。還可以使用$ apt-get install erlang
在 Ubuntu 或 Debian 發行版上安裝 Erlang。
對於其他 UNIX® 和 Linux 平台,可以下載源代碼並手工構建它。從源代碼構建 Erlang 需要 C 編譯器和 make 工具(參見 參考資料)。基本步驟如下:
-
解壓源代碼:
$ tar zxf otp_src_R14B01.tar.gz
-
切換目錄:
$ cd otp_src_R14B
-
運行配置腳本:
$ ./configure
-
運行 make 以構建代碼:
$ make
還可以從 Erlang 網站獲得 Windows® 安裝程序(參見 參考資料)。
第一個 Erlang 程序,一個遞歸的 Fibonacci 函數
要想了解函數編程風格的好處以及它在 Erlang 中的實現方式,最好的方法是了解 Fibonacci 函數。Fibonacci 數列是一種整數序列,可以使用以下算式計算各個 Fibonacci 值:F(n) = F(n-1) + F(n-2)
。
第一個值 F(0)
的結果是 0
,F(1)
的結果是 1
。在此之后,通過把前兩個值相加求出 F(n)
。例如,F(2)
的計算過程見 清單 1。
清單 1. F(2)
的計算過程
F(2) = F(2-1) + F(2-2) F(2) = F(1) + F(0) F(2) = 1 + 0 F(2) = 1
Fibonacci 數列對於許多系統(包括分析金融數據)都很重要,它還是在樹結構的主干和分支上安排葉節點的基礎。如果您玩過使用 3D 樹的視頻游戲,就會知道,這類游戲很可能使用 Fibonacci 數列來確定分支和葉的位置。
在用編程語言編寫 Fibonacci 計算時,可以使用遞歸來實現,即函數通過調用本身從 root(F(0)
和 F(1)
)開始計算數字。
在 Erlang 中,可以用變量和固定的值創建函數。這樣可以簡化 Fibonacci 數列的計算,因為 F(0)
和 F(1)
返回的是固定的值,而不是計算出的值。
因此,基本函數有三種情況:提供的值是 0
、1
和任何更高的值。在 Erlang 中,使用分號分隔語句,所以可以用 清單 2 所示的代碼定義基本 Fibonacci 函數。
清單 2. 基本 Fibonacci 函數
fibo(0) -> 0 ; fibo(1) -> 1 ; fibo(N) when N > 0 -> fibo(N-1) + fibo(N-2) .
第一行定義調用 fibo(0)
的結果(->
把定義與函數體分隔開),第二行定義調用 fibo(1)
的結果,第三行定義在提供正值 N
時執行的計算。可以這樣做是因為在 Erlang 中有一個稱為模式匹配的系統,后面會詳細討論這個系統。注意,最后一個語句(和 Erlang 中的所有語句)以句號結尾。實際的計算非常簡單。
現在,我們來仔細查看一下 Erlang 語言的結構。
基礎知識
如果您習慣了 Perl、Python 或 PHP 等語言,那么 Erlang 的結構和布局看起來可能有點兒怪,但它的某些方面會極大地簡化應用程序的編寫過程,讓您不必為代碼的許多方面操心。尤其是,Erlang 代碼比其他語言少得多,某些操作、表達式和構造往往只有一行。
了解 Erlang 最簡便的方法是使用 Erlang shell。安裝 Erlang 之后,可以通過在命令行上執行 erl
運行 Erlang shell,參見 清單 3。
清單 3. 使用 Erlang shell
$ erl Erlang R13B04 (erts-5.7.5) [source] [rq:1] [async-threads:0] Eshell V5.7.5 (abort with ^G) 1>
可以在提示符下輸入語句(語句應該以句號結尾)。shell 會執行語句。因此,輸入一個簡單的求和語句會返回 清單 4 所示的結果。
清單 4. 輸入簡單的求和語句
1> 3+4. 7
下面使用 shell 研究一些數據類型和構造。
數據類型
Erlang 支持基本數據類型(比如整數和浮點數)和更復雜的結構(比如元組和列表)。
整數和大多數整數操作與其他語言相同。可以把兩個數字相加,參見 清單 5。
清單 5. 將兩個數字相加
1> 3+4. 7
可以使用圓括號組織算式,參見 清單 6。
清單 6. 使用圓括號組織算式
2> (4+5)*9 2> . 81
注意,在清單 6 中結束語句的句號在另一行上,輸入句號之后方可執行前面的計算。
在 Erlang 中,會使用浮點數代表實數,並且可以自然地表達浮點數,參見 清單 7。
清單 7. 自然地表示浮點數
3> 4.5 + 6.2 . 10.7
還可以使用指數表示浮點數,參見 清單 8。
清單 8. 使用指數表示浮點數
4> 10.9E-2 +4.5 4> . 4.609
在整數和浮點數上,都支持使用標准的數學操作符(+、-、/ 和 *),可以在算式中混合使用浮點數和整數。但是,如果對浮點數使用取模和求余數操作符,則會產生錯誤,因為這些操作符只支持整數。
原子值
原子值是靜態的(即不變的)字面值。清單 9 給出一個示例。
清單 9. 原子值
8> abc. abc 9> 'Quoted literal'. 'Quoted literal'
原子值的使用方式應該與 C 中的 #define
相同,也就是說,作為一種明確指定或標識值的方法。因此,對於原子值,惟一合法的操作是比較。還可以將原子值的這種使用方法擴展到布爾邏輯,利用原子值 true 和 false 來標識語句的布爾結果。原子值必須以小寫字母開頭,否則需要加上單引號。
例如,可以比較整數並獲得布爾原子值結果,參見 清單 10。
清單 10. 比較整數以獲得布爾原子值
10> 1 == 1. true
還可以比較原子值,參見 清單 11。
清單 11. 比較原子值
11> abc == def. false
原子值本身按字母表次序排序(即 z
的值大於 a
),參見 清單 12。
清單 12. 原子值按字母表次序排序
13> a < z. true
可以使用標准的布爾操作符,比如 and
、or
、xor
和 not
。還可以使用 is_boolean()
函數檢查提供的值是 true 還是 false。
元組
元組是復合的數據類型,用於存儲數據項的集合。元組要放在花括號中(參見 清單 13)。
清單 13. 元組
14> {abc, def, {0, 1}, ghi}. {abc,def,{0,1},ghi}
一個元組的內容不必都是相同類型的。元組的構造很特殊,其中的第一個值為原子值。在這種情況下,第一個原子值稱為標簽,可以使用它來標識內容或對內容進行分類(參見 清單 14)。
清單 14. 第一個值為原子值的元組
16> { email, 'example@example.org'}. {email,'example@example.org'}
在這里標簽是 email,可以使用標簽標識此元組中其余的內容。
元組對於包含定義的元素和描述各種復雜數據結構非常有用。Erlang 允許顯式地設置和獲取元組中的值(參見 清單 15)。
清單 15. 顯式地設置和獲取元組中的值
17> element(3,{abc,def,ghi,jkl}). ghi 18> setelement(3,{abc,def,ghi,jkl},mno). {abc,def,mno,jkl}
注意,元組元素以 1
作為第一個值的索引,而不是像其他大多數語言中那樣從 0 開始。還可以將元祖作為整體進行比較(參見 清單 16)。
清單 16. 作為整體比較元組
19> {abc,def} == {abc,def}. true 20> {abc,def} == {abc,mno}. false
列表
最后一個數據類型是列表,列表用方括號表示。列表與元組相似,但是元組只能在比較中使用,而列表允許執行的操作更多。
基本的列表如 清單 17 所示。
清單 17. 基本的列表
22> [1,2,3,abc,def,ghi,[4,56,789]]. [1,2,3,abc,def,ghi,[4,56,789]]
字符串實際上是特殊類型的列表。Erlang 不直接支持字符串的概念,但是可以使用帶雙引號的值創建字符串值(參見 清單 18)。
清單 18. 使用帶雙引號的值創建字符串值
23> "Hello". "Hello"
但是,字符串實際上只是由 ASCII 字符值組成的列表。因此,上面的字符串存儲為由 ASCII 字符值組成的列表(參見 清單 19)。
清單 19. 字符串存儲為由 ASCII 字符值組成的列表
24> [72,101,108,108,111]. "Hello"
還可以使用 $Character
表示法指定字符(參見 清單 20)。
清單 20. 使用 $Character
表示法指定字符
25> [$H,$e,$l,$l,$o]. "Hello"
列表(包括字符串,即字符的列表)支持許多操作。這是字符串與原子值之間的主要區別。原子值是靜態的標識符,但是可以通過操作字符串的組成部分(各個字符)來操作字符串。例如,不能標識原子值(比如 'Quick brown fox'
)中的各個單詞,因為原子值是單一實體。但是,您可以把字符串分割為單詞:["Quick","brown","fox"]
。
lists 模塊中提供了許多用於操作列表的函數。例如,可以使用 sort 函數對列表中的數據項進行排序。因為這些是內置的函數,所以必須指定模塊和函數名(參見 清單 21)。
清單 21. 指定模塊和函數名
34> lists:sort([4,5,3,2,6,1]). [1,2,3,4,5,6]
列表操作
可以使用構造函數構造包含多個元素的列表,它用一個元素和另一個列表構造列表。在其他語言中,這種構造操作由用於 push()
的函數或操作符處理。在 Erlang 中,使用 |
(管道)操作符分隔頭(列表的開頭)和尾,表達方式為 [Head|Tail]
。頭是單一元素,尾是列表的其余部分。
清單 22 展示了如何在列表的開頭添加新的值。
清單 22. 在列表的開頭添加新的值
29> [1|[2,3]]. [1,2,3]
可以重復執行這種操作以構造整個列表,參見 清單 23。
清單 23. 重復執行這種操作以構造整個列表
31> [1|[2|[3|[]]]]. [1,2,3]
在這個示例中,末尾的空列表意味着您構造了一個結構良好(合適)的列表。注意,第一項必須是一個元素,不能是列表片段。如果以其他方式執行合並,則會構造一個嵌套式列表(參見 清單 24)。
清單 24. 構造嵌套的列表
30> [[1,2]|[2,3]]. [[1,2],2,3]
最后,可以使用 ++
操作符合並列表,參見 清單 25。
清單 25. 使用 ++
操作符合並列表
35> [1,2] ++ [3,4]. [1,2,3,4]
還可以從操作符左邊的列表中刪除右邊列表中的所有元素(參見 清單 26)。
清單 26. 刪除列表中的元素
36> [1,2,3,4] -- [2,4]. [1,3]
因為字符串是列表,所以這些操作也適用於字符串(參見 清單 27)。
清單 27. 這些操作也適用於字符串
37> "hello" ++ "world". "helloworld" 40> ("hello" -- "ello") ++ "i". "hi"
盡管這里只簡要介紹了數據類型,但是我們希望讓您對基本數據類型和操作有足夠的了解。
表達式和模式匹配
在研究數據類型時,我們已經看到了許多表達式和構造。表達式的重要元素是變量。Erlang 中的變量必須以大寫字母開頭,后面是大寫字母、小寫字母和下划線的任意組合(參見 清單 28)。
清單 28. Erlang 中的變量
41> Value = 99. 99
在 Erlang 中,在對變量賦值時,是一次性將值綁定到變量。綁定變量之后,就不能改變它的值(參見 清單 29)。
清單 29. 將值綁定到變量
42> Value = 100. ** exception error: no match of right hand side value 100
這與大多數語言不一樣 — 變量的定義通常意味着值是可變的。在 Erlang 中,只能賦值一次意味着:如果希望向計算某個值的結果,則必須將它賦值給新的變量(參見 清單 30)。
清單 30. Erlang 中變量的限制
43> Sum = Value + 100 199
只能賦值一次的好處是,在計算過程中很難意外地設置或改變變量值,這讓值的識別和調試變得更容易,也讓代碼更為清晰,有時候更簡短(因為可以簡化結構)。
注意,這種操作意味着先計算出值,然后把值綁定到變量。在其他語言中,可以根據函數或操作的引用設置值,這意味着值取決於訪問它時引用的值。在 Erlang 中,在創建變量時它的值總是已知的。
可以使用 f(Varname)
顯式地忽略一個變量的綁定,或使用 f()
忽略所有變量的綁定。
為變量賦值實際上就是一種特殊的模式匹配。Erlang 中的模式匹配還會處理各個語句的執行流,以及從復合數據類型(元組、數組)中提取出值。模式匹配的基本形式是:模式 = 表達式。
表達式由數據結構、綁定的變量(即具有值的變量)、數學操作符和函數調用組成。操作的兩邊必須匹配(也就是說,如果模式是包含兩個元素的元組,那么表達式的計算結果也必須是包含兩個元素的元組)。在執行表達式時,計算表達式並將結果賦值給模式。
例如,可以使用一個模式匹配同時為兩個變量賦值(參見 清單 31)。
清單 31. 同時給兩個變量賦值
48> {A,B} = {(9+45),abc}. {54,abc}
注意,如果模式是綁定的變量,或者模式的元素是綁定的變量,那么模式匹配的結果就會變成比較。此操作支持實現強大的選擇性賦值。例如,為了從元組中獲取姓名和電子郵件地址,可以使用以下模式匹配:{contact, Name, Email} = {contact, "Me", "me@example.com"}
。
最后,可以按前面提到的構造表示方法,使用模式匹配從列表或元組中提取元素。例如,清單 32 展示了如何獲得列表中的前兩個元素,同時保留其余元素。
清單 32. 獲得列表中的前兩個元素,同時保留其余元素
53> [A,B|C] = [1,2,3,4,5,6]. [1,2,3,4,5,6]
A
賦值為 1
,B
賦值為 2
,C
賦值為列表的其余部分。
函數
與其他語言一樣,Erlang 中的函數是所有程序的基本組成部分。函數由函數名(由一個原子值定義)和圓括號中的零個或更多函數參數組成:sum(N,M) -> N+M
。
函數必須在文件中的模塊中定義(不能在 shell 中定義函數)。參數可以包含復合數據類型。例如,可以使用元組中的標簽選擇不同的操作(參見 清單 33)。
清單 33. 可以使用元組中的標簽選擇不同的操作
mathexp({sum, N,M}) -> N+M ; mathexp({sub, N,M}) -> N-M ; mathexp({square, N}) -> N*N.
分號是每個函數定義之間的 “或” 操作符。使用模式匹配評估函數的參數,所以如果把包含三個元素的元組提供給 mathexp()
函數,模式匹配會失敗。
Erlang 中的函數還可以接受不同數量的參數。Erlang 會執行模式匹配,直到找到有效的函數定義,從而選擇適當的函數定義。函數的參數數量稱為元數 (arity),用於幫助標識函數。
再看一下 Fibonacci 示例,現在您就會發現,當調用 fibo(0)
時,模式與函數的第一個定義匹配,fibo(1)
與第二個定義匹配,其他值與最后一個定義匹配。這也解釋了函數執行的遞歸是如何實現的。例如,在調用 fibo(9)
時,可以使用相應的值調用 fibo(N)
函數定義,直到到達fibo(0)
和 fibo(1)
函數定義(它們返回固定的值)。
任何函數的返回值都是子句(在我們的示例中只有一行)中最后一個表達式的結果。注意,只有在找到了匹配項且變量是函數局部變量的情況下,才會為變量賦值。
模塊
與其他語言中的模塊一樣,模塊用於把相似的函數集中在一起。
在文件中指定模塊名(必須與文件名匹配),然后指定模塊中希望導出到裝載此模塊的其他程序的函數。例如,清單 34 給出了文件 fib.erl,其中包含 fib 模塊的定義。
清單 34. fib.erl 文件
-module(fib). -export([fibo/1, printfibo/1]). %% print fibo arg. and result, with function as parameter printfibo(N) -> Res = fib:fibo(N), io:fwrite("~w ~w~n", [N, Res]). fibo(0) -> 0 ; fibo(1) -> 1 ; fibo(N) when N > 0 -> fibo(N-1) + fibo(N-2) .
模塊聲明位於 -module()
行中。-export()
行包含要導出的函數的列表。每個函數的定義都給出了函數名和函數的元數,以便您能導出函數的特定定義。
要使用模塊,則需要編譯並裝載模塊。可以在 shell 中使用 c()
語句完成這個步驟,參見 清單 35。
清單 35. 使用 c()
語句編譯並裝載模塊
1> c(fib). {ok,fib} 2> fib:printfibo(9). 9 34 ok
注意,函數調用包含模塊名,從而確保調用的是 fib 模塊中的 printfibo()
函數。
結束語
Erlang 的結構和格式與大多數其他語言有很大區別。盡管許多數據類型和基本的表達式是相同的,但是它們的用法和應用不太一樣。變量只能賦值一次,通過模式匹配系統對不同的表達式進行運算,這些特性給典型的語言環境提供了一些強大的擴展。例如,可以為同一函數定義多種處理方式,還可以對遞歸調用應用模式匹配,這樣做可以簡化某些函數。
在下一篇文章中,我們將討論 Erlang 的進程、消息解析和網絡功能,並通過研究 MochiWeb HTTP 服務器了解這種語言的強大功能和靈活性。
參考資料
學習
-
Erlang 網站:尋找關於這種編程語言的更多信息。
-
Erlang 的 Wikipedia 條目:進一步了解 Erlang 的歷史。
-
由 Martin Logan、Eric Merritt 和 Richard Carlsson 合著並由 Manning Publications 出版的Erlang and OTP in Action 講解了如何使用 Erlang 和 OTP 框架創建實用的應用程序。
-
由 Francesco Cesarini 和 Simon Thompson 合著並由 O'Reilly Media 出版的Erlang Programming: A Concurrent Approach to Software Development 深入解釋了 Erlang,這種編程語言非常適合要求並發性、容錯能力和快速響應的任何場合。
-
由 Joe Armstrong 撰寫並由 Pragmatic Programming 出版的 Programming Erlang: Software for a Concurrent World 講解如何用 Erlang 編程語言編寫高可靠性的應用程序(甚至能夠應對網絡和硬件故障)。
-
developerWorks Web development 專區:尋找討論各種基於 web 的解決方案的文章。
-
developerWorks 演示中心:觀看免費的演示,了解 IBM 及開放源碼技術和產品功能。
-
訪問 developerWorks Open source 專區獲得豐富的 how-to 信息、工具和項目更新以及最受歡迎的文章和教程,幫助您用開放源碼技術進行開發,並將它們與 IBM 產品結合使用。
獲得產品和技術
-
Erlang:下載 Erlang 編程語言。
-
Apache CouchDB 項目:CouchDB 是使用 MochiWeb HTTP 服務器用 Erlang 編寫的,可以從 Apache 獲取它。