erlang:世界是平行的!epaper.gotop.com.tw/pdfsample/axp012800.pdf · y...

30
譯者序 Erlang:世界是平行的! 如果你像我一樣每個月持續追蹤 Tiobe 的編程語言需求排行榜,你應該也注 意到了,這兩個月名次竄升最猛烈的語言是 Erlang,一口氣約上升 15 個名 次,好像有一點開始紅的跡象。事實上,在電信產業,Erlang 早就是最重要 的語言之一。 大多數的電信公司,都會用到 Erlang 來開發分散式系統,以達到共時 Concurrent )與容錯(Fault-Torrent )的能力。除了共時與容錯,現在多核 心與 HT (超執行緒)的 CPU 環境,也提供 Erlang 語言相當好的發揮環境。 Erlang 是由瑞典電信大學所創造出來的。後來移轉到 Ericsson(瑞典電信巨 擘)的 OTP (開放電信平台)實驗室,後來又被釋出,成為開放源碼的計畫。 所以,現在 Erlang 不是任何公司的私有財產,這有助於 Erlang 的推廣。 Erlang 雖然和電信產業的淵源很深,但是 Erlang 沒有劃地自限,用它來開發 財務系統或各種伺服器系統,也不少見。總之,只要想開發出分散式、多核 心、容錯、共時的系統,就相當適合使用 ErlangErlang 提出一種所謂的「共時導向編程」( Concurrency-Oriented ProgrammingCOP)的概念,讓我們可以輕易地寫出共時的程式。共時、 平行(parallel )運算、多工(multi-task)、多執行緒(多線程),其實都是 一樣的概念,儘管各個語言或平台的作法可能不盡然相同。

Upload: others

Post on 26-Jul-2020

10 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

譯者序

Erlang:世界是平行的!

如果你像我一樣每個月持續追蹤 Tiobe的編程語言需求排行榜,你應該也注

意到了,這兩個月名次竄升最猛烈的語言是 Erlang,一口氣約上升 15 個名

次,好像有一點開始紅的跡象。事實上,在電信產業,Erlang早就是最重要

的語言之一。

大多數的電信公司,都會用到 Erlang 來開發分散式系統,以達到共時

(Concurrent)與容錯(Fault-Torrent)的能力。除了共時與容錯,現在多核

心與 HT(超執行緒)的 CPU環境,也提供 Erlang語言相當好的發揮環境。

Erlang是由瑞典電信大學所創造出來的。後來移轉到 Ericsson(瑞典電信巨

擘)的 OTP(開放電信平台)實驗室,後來又被釋出,成為開放源碼的計畫。

所以,現在 Erlang不是任何公司的私有財產,這有助於 Erlang的推廣。

Erlang雖然和電信產業的淵源很深,但是 Erlang沒有劃地自限,用它來開發

財務系統或各種伺服器系統,也不少見。總之,只要想開發出分散式、多核

心、容錯、共時的系統,就相當適合使用 Erlang。

Erlang 提出一種所謂的「共時導向編程」( Concurrency-Oriented

Programming,COP)的概念,讓我們可以輕易地寫出共時的程式。共時、

平行(parallel)運算、多工(multi-task)、多執行緒(多線程),其實都是

一樣的概念,儘管各個語言或平台的作法可能不盡然相同。

Page 2: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

iii 譯者序

如果我們想要寫程式,使它像真實世界的物件一樣地運作,那麼這樣的程式

就會用到共時的(concurrent)結構。如果使用「專為共時應用設計」的語

言,來開發這樣的應用,一切就會簡單許多。而目前最棒的共時編程語言,

應該就是 Erlang了。Erlang可以幫助我們,將我們思考和互動的方式,寫成

程式。

使用 Erlang開發系統的好處包括了:

寫出來的程式,移到多核心的環境中執行,速度會自然變快(甚至有

可能達到線性加速,n個核心就提升 n倍)。

可以寫出容錯的系統,當機之後會重新啟動。

可以寫出「程式碼熱抽換」的系統,可以一邊執行一邊升級,不用先

暫停服務。

寫出來的程式不可思議地精簡。

對 Erlang語言來說,共時是屬於語言的一部份,而不是作業系統的一部份。

Erlang讓平行編程變得容易,作法是將世界塑模(modeling)成一組平行的

行程(process),彼此之間只能透過訊息交換來互動(這裡所謂的行程,和

OS的行程不一樣)。在 Erlang的世界,有平行行程,但是沒有上鎖(lock)、

沒有同步化方法(synchronized method)、沒有共享記憶體而造成資料混亂

的可能。Erlang程式可以由數千甚至上百萬個極度質輕的行程所組成,這些

行程可以在一個多核心處理器上執行,或者在許多處理器所形成的網路上執

行。

Erlang提供了 Mnesia的 Erlang資料庫管理系統(DBMS)。Mnesia是整合

式的 DBMS,存取速度相當快。它可以被設定成跨越數個實體分離的節點進

行資料複製,以提供容錯的操作。Mnesia不是一般的 DBMS,它不使用 SQL

語言,而是使用自己的「list comprehension」語法進行資料庫的存取。

來一則軼事吧!Mnesia 這個名稱是有典故的,開發團隊最早為它取名為

amnesia(失憶症),但是老闆不同意:「資料庫有失憶症,成何體統」,

於是把字頭的 a去掉,變成 Mnesia。

Page 3: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 iv

除了 Mnesia之外,用 Erlang開發系統,也幾乎都會用到 OTP(開放電信平

台)程式庫。OTP 是一組 Erlang 程式庫與開放源碼程序,用來幫助 Erlang

程式建立工業等級的應用。儘管 OTP 的名稱有「電信」字眼,但用途是沒

有限制的。OTP是 Erlang威力的來源之一。利用 OTP寫出牢靠的伺服器是

相當容易的事。

和我之前學過的語言相比,Erlang是一個很古怪的語言,具有相當陌生的語

法,甚至還不支援物件導向。不只程式與眾不同,整個編程採用的手法也不

一樣。但是語法又相當簡潔有力。

我發現我很喜歡 Erlang 語言,因為它夠奇怪(太普通的語言,我是沒興趣

的)。沒想到 Erlang光是透過 pattern matching的語法,居然可以做這麼多

事,而且相當精簡;透過 messaging的方式,就可以輕易地做出多工、容錯、

分散式的系統;Erlang還大量地使用 higher-order function,已經到了無孔不

入的地步。

只要你的腦筋轉得過來,我覺得 Erlang算是相當精簡好學的語言。如果你有

分散式、容錯、多核心的開發需求,你應該將 Erlang列入考慮。過去這兩年,

我們品嚐過來自日本的 Ruby,現在你準備好品嚐下一道菜 – 來自北歐的

Erlang嗎?

蔡學鏞(Jerry Tsai)

April, 2008

Page 4: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

開始學習

2.1 概觀

就和一般的學習一樣,在精通 Erlang的過程中,你也會經過一些階段。讓我

們看看本書涵蓋哪些階段,認清你一路上會體驗到的事有哪些。

第 1階段:我覺得陌生

身為一個初學者,你將要學習如何啟動系統,在 Shell 中執行命令,編譯簡

單的程式,然後漸漸熟悉 Erlang(畢竟 Erlang是個小型語言,所以這個階段

不會維持太久)。讓我們將此階段分成更細的數個小階段,身為初學者,你

將會做下面的事:

確定在你的電腦上有一套合用的 Erlang系統。

學習啟動與停止 Erlang Shell。

發現如何在 Shell中輸入表示式,估算(evaluate)表示式,且瞭解估

算結果。譯者註

知道如何使用你慣用的文字編輯器來建立並修改程式。

在 Shell中編譯並執行你的程式。

譯者註 估算、求值、執行,都是一樣的概念,可能會交替出現。

Page 5: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 8

第 2階段:我開始習慣

到目前為止,你將會具有此語言的一般常識。如果你遇到語言問題,你會具

有足夠的背景,可以從第 5章〈進階序列式編程〉中自行學習。

在此階段,你會熟悉 Erlang,可以進入更有趣的主題:

你會學到 Shell 的進階用法,比一開始時學到的更多(比方說,你可

以召回之前的表示式,以進行編輯,這樣的內容包含在 6.5 節〈在

Erlang Shell中進行命令編輯〉)。

你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程

式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

因此本書大量使用這幾個模組。

隨著你的程式越來越大,你會學習到如何自動化編譯與執行。我們使

用的工具是 make。我們會看到如何透過寫 makefile來控制此過程。這

是第 6.4節〈用 Makefile進行自動編譯〉的內容。

Erlang 編程有一個更大的世界,使用一個名為 OTP1的大型程式庫集

合。當你的 Erlang經驗越來越多,你會發現 OTP可以讓你省下許多

時間,畢竟如果你想要的功能已經有人事先寫好了,你何需自己重頭

再做一次?我們會學習 OTP主要的行為,特別是 gen_server,這是

第 16.2節〈開始使用 gen_server〉的內容。

Erlang 主要的用途之一是寫出分散式(distributed)程式,現在是開

始體驗它的時機。你可以從第 10 章〈分散式編程〉的範例開始,將

這些範例擴充成你要的樣子。

第 2.5階段:我可能選擇多學一些

第一次閱讀本書的時候,你不需要逐章閱讀。Erlang和你之前使用過的大部

分語言不同,Erlang是 concurrent(共時)編程語言 — 這使得它相當適合用

1 開放電信平台(Open Telecom Platform)

Page 6: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

9 Chapter 2 開始學習

來寫出分散式程式,適合現代多核心 CPU或 SMP2 電腦。只要使用多核心或

SMP的機器,大多數的 Erlang程式就會執行得更快(無須修改程式)。

Erlang 編程牽涉到使用一種我所謂的共時導向編程(concurrency-oriented

programming,COP)的編程範式(programming paradigm)。

當你使用 COP 時,你會將問題分解成小問題,在這些解決方案中找出自然

的共時。這是寫任何共時程式時,很重要的第一個步驟。

第 3階段:叫我 Erlang大師

現在你已經精通此語言,且可以寫出一些有用的分散式程式。但是想成為真

正的大師,你需要學更多:

Mnesia 資料庫。Erlang 軟體內建一個快速的資料庫,名為 Mnesia。

Mnesia 原本是設計給電信軟體使用,所以特別考慮到效能與容錯

(fault tolerance)的需求,現在即使是非電信軟體也常使用 Mnesia。

和其他編程語言之間如何銜接,如何使用連入驅動程式(linked-in

driver)。請見 12.4節〈連入驅動程式〉。

完整地使用 OTP建立行為的監督樹、開始劇本…等。請見第 18章〈用

OTP開發系統〉。

如何執行程式,如何為多核心電腦優化程式。請見第 20 章〈為多核

心 CPU編程〉。

最重要的一課

看本書時,你需要隨時記得一個規則:編程是好玩的。而我個人認為,編寫

分散式應用(像是聊天程式或即時通訊應用),更是比「我們慣常編寫的序

列式應用」有趣。你能在一部電腦上做的事相當有限,但是有了電腦網路,

2 SMP = 對稱多處理器(Symmetric multiprocessing)

Page 7: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 10

應用無限寬廣。Erlang提供了理想的環境,讓你可以實驗網路應用,建立出

具有實用品質的系統。

為了幫助你從一開始就能體會到這一點,我會在技術的章節內混合一些真實

世界的應用。你可以將這些應用當作你自行實驗的起點,用我料想不到的方

式修改它們、部署它們,我很樂見你這麼做。

2.2 安裝 Erlang

在你能做任何事之前,你必須確定你的電腦上已經安裝好 Erlang。打開命令

提示字元(也就是 console視窗),鍵入 erl,如下所示:

$ erl

Erlang (BEAM) emulator version 5.5.2 [source] ... [kernel-poll:false]

Eshell V5.5.2 (abort with ^G)

1>

在 Windows作業系統上,必須安裝好 Erlang,且做好 PATH環境變數的設

定,才會成功。假設你用標準的作法安裝程式,你將可以透過「開始」>

「所有程式」>「Erlang OTP」選單,取用 Erlang。在附錄 B,我會告訴

你如何操控 Erlang,讓它和 MinGW與 MSYS搭配執行。

注意:我只有偶而才會展示此標語(也就是『Erlang (BEAM) ... (abort with

^G)』這段文字)。只有當你想要提出 bug 報告時,此資訊才會派上用場。

我在此展示它,好讓你第一次看到它時不會太驚訝,不知道這是什麼。大多

數的時候,我會將它省略不顯示,除非真的有必要顯示。

如果你看到此 shell標語,就表示 Erlang確實有安裝在你的電腦上,先離開

它(按下 Ctrl+G,後面跟著字母 Q,然後按下 Enter或 Return)3。現在你可

以跳到 2.3節〈本書的程式碼〉。

3 或者在 shell中使用 q()命令。

Page 8: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

11 Chapter 2 開始學習

如果你得到的是錯誤訊息,告訴你 erl是未知的命令,就表示你需要先安裝

Erlang。這個時候你就需要做一個決定 — 你想使用預先建置好的二元版本?

或使用包裝好的版本(OS X)?或自行編譯 Erlang 的源碼?或使用

Comprehensive Erlang Archive Network(CEAN)?

二元版本

Erlang準備有 Windows和 Linux的二元散佈版本,安裝二元版本的方式會因

為 OS不同而有差異,讓我們分別解說。

Windows

你會在 http://www.erlang.org/download.html看到一個清單。選擇最新版本,

選擇 Windows二元檔的連結(指向 Windows執行檔)。點擊此連結,然後

依照指示動作,這是標準的 Windows 安裝方式,所以過程應該不會有什麼

問題。

Linux

Linux版本的二元檔案相容於 Debian系統(以及其衍生系統),使用下面的

命令:

> apt-get install erlang

Mac OS X

如果你使用 Mac,你可以利用 MacPorts 系統來安裝預先建置好的 Erlang,

或者你可以自行編譯源碼。使用 MacPorts 是最簡單的作法,且會自行處理

版本更新。然而,MacPorts可能會有一點版本落後的狀況發生,比方說,在

我開始寫本書的時候,Erlang的 MacPort版本比最新版落後兩個版本。為此,

我建議你不如忍受一時的痛苦,自行編譯 Erlang源碼,下一節會告訴你怎麼

做。要這麼做,你必須確定你已經事先具有開發工具(在隨機附上的光碟片

中,有這樣的工具軟體)。

Page 9: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 12

從源碼建置 Erlang環境

如果不進行二元安裝,另一種作法是從源碼建立 Erlang。對於 Windows 來

說,這麼做其實沒有什麼優點,因為只要有新版本就會提供二元檔。但是對

於 Mac和 Linux平台來說,二元檔可能會來得晚一些。對於任何 Unix類的

OS,安裝方法都是一樣的:

1. 取得最新的 Erlang 源碼 4。源碼會被包在一起,檔案名稱類似

otp_src_R11B-4.tar.gz(表示 Erlang第 11版的第 4個維護釋出版本)。

2. 解開打包、設定組態、make成二元檔、安裝。作法如下: $ tar -xzf otp_src_R11B-4.tar.gz

$ cd otp_src_R11B-4

$./configure

$ make

$ sudo make install

注意:在建置系統前,你可以使用「./configure --help」命令,來得知可

用的組態選項有哪些。

使用 CEAN

CEAN(Comprehensive Erlang Archive Network)試圖將所有主要的 Erlang

應用集中在一地,具有共同的安裝程式。使用 CEAN的優點是,它不只管理

基本的 Erlang系統,也具有用 Erlang開發出來的許多包裹(Package)。這

意味著除了可以保持 Erlang環境在最新版,也可以一併維護這些包裹。CEAN

預先為各種 OS和 CPU編譯好各種二元檔。想利用 CEAN來安裝系統,使

用 http://cean.process-one.net/download/,並依照指示去做。(請注意,有些

讀者回報 CEAN 可能不會安裝 Erlang 編譯器,如果這樣的情況發生在你身

上,那麼啟動 Erlang shell,使用命令「cean:install(compiler)」,就可以

安裝編譯器。)

4 http://www.erlang.org/download.html

Page 10: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

13 Chapter 2 開始學習

2.3 本書的程式碼

我們展示過的大多數程式碼片段,都是取自完整的可執行範例,你可以下載

這些範例5。如果是出自可以下載的程式,程式上方就會出現一個橫條(如下

所示):

下載 shop1.erl

-module (shop1).

-export ([total/1]).

total([{What, N}|T]) -> shop:cost(What) * N + total(T);

total([]) -> 0.

此橫條包含程式碼的路徑。如果你閱讀本書的 PDF版本,且你的 PDF閱覽

器支援超連結,則你可以直接點擊此橫條,程式就會出現在一個瀏覽器的視

窗上。

2.4 啟動 Shell

現在讓我們開始吧!我們可以使用 Shell和 Erlang進行互動。一旦我們啟動

Shell,我們可以鍵入表示式(expression),且 Shell將會顯示出表示式的值。

如果你已經在系統上安裝了 Erlang(依照 2.2節〈安裝 Erlang〉的描述),

那麼此 Erlang Shell(也就是 erl)也就被安裝好了。想執行它,打開你慣用

的 OS Shell,(例如 Windows的 cmd或 Unix系統的 bash),在命令提示符

號下,鍵入 erl,就可以啟動 Erlang Shell:

$ erl

Erlang (BEAM) emulator version 5.5.1 [source] [async-threads:0] [hipe]

Eshell V5.5.1 (abort with ^G)

1> % I'm going to enter some expressions in the shell ..

1> 20 + 30.

50

2>

5 http://pragmaticprogrammer.com/titles/jaerlang/code.html

Page 11: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 14

讓我們看看,我們剛剛做了什麼:

這是 Unix命令,用來啟動 Erlang Shell,回應會告訴我們 Erlang是那個

版本。

Shell會印出提示符號 1>,然後我們鍵入一個註解(comment)。百分比

(%)字元表示開始進入註解,一直到該行結束。註解內容會被 Erlang編

譯器忽略不理會。

因為命令尚未鍵入完畢,所以提示符號「1>」重複出現,現在我們鍵入

表示式「20 + 30」,後面跟著一個英文句號和一個換行。(初學者常常

會忘了鍵入句號,這會導致 Erlang不知道我們的表示式已經完畢,所以

就不會看到結果被顯示出來。)

Shell會計算出表示式,然後印出結果(本範例的結果是 50)。

Shell印出另一個提示符號,這次命令編號是 2(每個新命令的編號都會

遞增)。

你在自己的電腦上試著執行過此 Shell 嗎?如果還沒有試過,現在就請你動

手做。光看書不動手,你可能會以為這樣你還是可以瞭解,但是你卻無法將

知識從腦袋轉移到手指 — 編程(編寫程式,programming)可不是觀賞運動

比賽,而比較像是參加運動比賽,多練習才會進步。

先鍵入一模一樣的表示式,然後試著修改做一些實驗。如果行不通,停下腳

步,問問自己,到底錯在哪裡。即使經驗豐富的 Erlang編程員也會花許多時

間和 Shell做互動。

隨著你越來越有經驗,你會發現此 Shell 是威力很強的工具。之前的 Shell

命令可以被召回(Ctrl+P和 Ctrl+N)並編輯修改(利用類似 emacs的編輯命

令)。這是 6.5節〈Erlang Shell命令編輯〉的內容。最棒的是,當你開始寫

分散式程式,你會發現你可以將一個 Shell依附(attach)到叢集(cluster)

內不同 Erlang節點(node)執行中的 Erlang系統,或用保全的 Shell(ssh)

連線直接連接到遠端電腦上執行的 Erlang 系統。你可以和任何 Erlang 節點

上的任何程式互動。

Page 12: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

15 Chapter 2 開始學習

警告:你無法將本書讀到的一切都鍵入 Shell。特別是,你無法將「列在

Erlang程式檔案中的程式碼」鍵入 Shell。在.erl檔案內的語法格式不是

表示式,所以 Shell 不瞭解。Shell 只能估算 Erlang 表示式,除此之外,

一律不懂。特別是,你不可以把模組註記(module annotation)鍵入 Shell

(也就是那些以減號開始的東西,例如-module、-export)。

本章剩下的部分,是許多和 Erlang Shell的簡短對話。我並不會解釋所有的

細節,因為這會將內文的流程打亂。在第 5.4節〈許多簡短主題〉,再說明

許多之前略過的細節。

2.5 簡單整數算術

讓我們計算一些算術表示式:

1> 2 + 3 * 4.

14

2> (2 + 3) * 4.

20

重要:你會看到這些對話都是以命令編號 1開始(也就是 Shell會印出「1>」)。

這意味著我們重新開始一個新的 Erlang Shell。每次你看到一個對話盒時,

只要一開始顯示「1>」,你都必須啟動新的 Shell(如果你想要確實重現本

書範例的環境)。當範例一開始的命令編號大於 1,這表示此 Shell會談是從

前面的範例接續下來的,因此你不需要啟動新的 Shell。

注意:如果你想要一邊閱讀本書,一邊在 Shell 鍵入這些範例(這當然是值

得鼓勵的行為),那麼你可能想要很快地看一下第 6.5 節〈在 Erlang Shell

中進行命令編輯〉。

你會看到 Erlang遵守正常的算術表示式規則,所以「2 + 3 * 4」是「2 + (3

* 4)」的意思,而不是「(2 + 3) * 4」。

Erlang使用任意長度的整數,來進行整數算術。在 Erlang中,整數算術是精

確的,因此你不用擔心算術溢位(overflow)的問題。

Page 13: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 16

Shell沒有回應嗎?

如果在你鍵入一個命令之後,Shell沒有回應,那麼你可能忘了鍵入「點

空白」(dot-whitespace),也就是一個英文句號後面跟著一個 Return。

另一件可能犯的錯是你想前後括上引號(單引號或雙引號),卻前後使

用不一致的引號。

上述任何一件事情發生,那麼最好鍵入多的一個關閉引號,後面跟著「點

空白」。如果狀況真的變得很糟糕,且系統完全沒有回應,那麼鍵入

Ctrl+C(在 Windows上,鍵入 Ctrl+Break)。你會看到下面的輸出:

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded

(v)ersion (k)ill (D)b-tables (d)istribution

現在按下 A,表示你要放棄(abort)目前的 Erlang會談(session)譯者註

進階:你可以啟動與結束多重 Shell,見 6.7節〈Shell沒有回應〉的詳

細說明。

何不試試看?你可以計算相當大的數字,讓你的朋友對 Erlang印象深刻:

3> 123456789 * 987654321 * 112233445566778899 * 998877665544332211.

13669560260321809985966198898925761696613427909935341

鍵入整數的方法有許多種6。下面是一個表示式,使用到基底 16 和基底 32

的記號法:

4> 16#cafe * 32#sugar.

1577682511434

2.6 變數

你要如何儲存命令的結果,供以後使用?這時候你需要變數,請看範例:

1> X = 123456789.

123456789

發生什麼事了?首先,我們將一個值指定給變數 X;然後 Shell 會印出此變

數的值。

譯者註 Session翻譯成「會談」,表示:一個程式的操作期間。 6 見 5.4節對整數的討論。

Page 14: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

17 Chapter 2 開始學習

變數記號法

常常我們想要討論特定變數的值。為此我們使用 Var Value 的記號

法,比方說,「A 42」表示變數 A 的值是 42。如果有數個變數,我

會寫成「{A 42, B true ... }」,表示 A是 42,B是 true…等等。

注意:所有的變數名稱必須用大寫字母開始。如果你想要看到變數的值,只

要輸入變數名稱即可:

2> X.

123456789

現在 X具有一個值了,你可以使用 X:

3> X*X*X*X.

232305722798259244150093798251441

然而,如果你試圖指定(assign)不同的值到變數 X,你會得到錯誤訊息:

4> X = 1234.

=ERROR REPORT==== 11-Sep-2006::20:32:49 ===

Error in process <0.31.0> with exit value:

{{badmatch,1234},[{erl_eval,expr,3}]}

** exited: {{badmatch,1234},[{erl_eval,expr,3}]} **

發生什麼事?想解釋,我就必須破除你對於「X = 1234」所做的兩個假設:

首先,X不是一個變數,至少不是像你在 Java和 C中所慣用的那樣。

第二,=不是指定運算子(assignment operator)。

對於 Erlang新手來說,這是最詭異的地方之一。讓我們花幾頁的篇幅,討論

更深入一些。

Page 15: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 18

單一指定就像是代數

當我在學校唸書時,我的數學老師說過『如果一個等式中,X出現多次,

那麼所有 X代表的是相同的值』,我們就是因為這樣才能解開方程式:

如果我們知道「X+Y=10」,且「X-Y=2」,那麼 X就會是 6,且 Y就

會是 4。

但是當我們學習第一個編程語言時,我們看到像下面這樣的式子:

X = X + 1

大家都搖頭,直說『這怎麼可能!』但是老師說我們錯了,我們必須暫

時把數學課學過的知識忘掉,X不是一個數學變數:它只是一個鴿子洞,

或者是一個小盒子........

在 Erlang中,變數就像是數學中的變數。當你將值和變數關連上,你其

實是在做聲明(assertion),也就是陳述一件事實:變數具有此值。就

是這樣。

不會變的變數

Erlang具有「單一指定」變數。顧名思義,單一指定變數只能夠被賦予值一

次。如果你試圖改變「已經被設定過」的變數值,你就會得到錯誤(事實上,

你會得到一個 badmatch 錯誤,如同上面的例子所示)。「具有指定值」的

變數,被稱為「已繫結」變數(bound variable);未具有值的變數,稱為「未

繫結」(unbound)變數。變數一開始都是未被繫結的。

當 Erlang看到像「X = 1234」這樣的敘述,它會將變數 X繫結到值 1234。在

被繫結之前,X 可以具有任何值:X 只是一個洞,等待被填入資料。然而,

一旦得到值,就會永遠持有。

此時你可能會納悶,為何我們稱呼這樣的東西為變數(variable),其實是有

兩個原因的:

它們是變數,不過只能被改變一次(也就是從「未繫結」到「具有一

個值」)。

Page 16: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

19 Chapter 2 開始學習

它們看起來像是一般編程語言的變數,所以當我們看到像「X = ...」

這樣的程式碼,我們的腦袋會說『我知道這是什麼;X 是一個變數,

且=是指定運算子』。這樣的看法幾乎是對的:X 幾乎是變數,且=幾

乎是指定運算子。

注意:在 Erlang程式碼中使用省略記號(...),表示我有省略一些程

式碼。

事實上,=是「模式比對」運算子,如果 X是未繫結變數,則=的行為就是一

般的指定。

最後,變數的範疇(scope)是「定義它的時候」所在的語彙單元。所以

如果 X 被用在單一函數條款(clause)內譯者註

,它的值不會『逃脫』到函

數條款外部。相同函數內的不同條款,共享所謂的全域與私用變數,對

Erlang來說是不可能的。如果許多不同函數內都有 X,那麼這些 X的值都

會不一樣。

模式比對

對於大多數的語言來說,=表示要指定變數的值。然而對 Erlang來說,=是指

「模式比對」(pattern matching)運算。Lhs = Rhs的意思是:估算右側(Rhs),

然後和左側(Lhs)比對結果。

現在變數(例如 X)是模式的一種簡單形式。我們之前也說過,變數只能被

給予值一次,當我們第一次說 X = SomeExpression的時候,Erlang會自問:

『如何才能讓此敘述成真?』因為 X 尚未有值,所以 Erlang 就把 X 繫結到

SomeExpression的值,然後此敘述就會變得正當(valid),皆大歡喜。

如果之後我們說 X = AnotherExpression,那麼只有當 SomeExpression 與

AnotherExpression完全等同時,才會成功。下面是一個範例:

譯者註 下一章會解釋何謂條款(clause)。

Page 17: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 20

1> X = (2+4).

6

2> Y = 10.

10

3> X = 6.

6

4> X = Y.

=ERROR REPORT==== 27-Oct-2006::17:25:25 ===

Error in process <0.32.0> with exit value:

{{badmatch,10},[{erl_eval,expr,3}]}

5> Y = 10.

10

6> Y = 4.

=ERROR REPORT==== 27-Oct-2006::17:25:46 ===

Error in process <0.37.0> with exit value:

{{badmatch,4},[{erl_eval,expr,3}]}

7> Y = X.

=ERROR REPORT==== 27-Oct-2006::17:25:57 ===

Error in process <0.40.0> with exit value:

{{badmatch,6},[{erl_eval,expr,3}]}

事情是這樣的:在第 1 行,系統執行表示式 2+4,得到答案是 6。這一行之

後,Shell會做下面的繫結設定:{X 6}。第 3行之後,繫結會變成{X 6,

Y 10}。

現在我們到第 5行。就在我們估算此表示式之前,我們知道 X 6,所以比

對 X = 6會成功。

當我們在第 7行說 X = Y,我們的繫結是{X 6, Y 10},因此比對失敗,

印出錯誤訊息。

第 4到第 7行的表示式,會成功或失敗都是根據 X與 Y的值而定。現在的時

機適合好好思考這一切,確定你真的瞭解之後,再繼續閱讀下去。

你可能會認為我太過於執著討論這一點,所有的模式中「=」左邊都只是變

數(不管有無繫結),但是我們稍後會看到,我們可以做出相當複雜的模式,

然後用「=」進行比對。在介紹過值組(tuple)與清單(list)(都是用來儲

存複合資料項目)之後,我們會回到這個主題。

1

-

-

-

5

-

-

-

-

10

-

-

-

-

15

-

-

-

-

20

Page 18: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

21 Chapter 2 開始學習

為何單一指定會讓程式更好?

在 Erlang中,變數只是某個值的一個「參考」(reference),也就是說,在

Erlang內部的實踐,被繫結變數其實是一個指標,指向真正儲存值的位置,

而此值是不能被改變的。

值不能被改變,這一點相當重要,這和 Java或 C這類命令式語言(imperative

language)的作法完全不同。

讓我們看看當你被允許改變變數時,會發生什麼事。先讓我們定義一個變數 X:

1> X = 23.

23

現在我們可以在運算中使用 X:

2> Y = 4 * X + 3.

95

現在假設我們可以改變 X的值(好恐怖):

3> X = 19.

幸好 Erlang不允許我們這麼做。Shell對此瘋狂之舉提出警告:

=ERROR REPORT==== 27-Oct-2006::13:36:24 ===

Error in process <0.31.0> with exit value:

{{badmatch,19},[{erl_eval,expr,3}]}

意思是,X已經是 23,所以不能是 19。

假設我們可以這麼做,那麼 Y的值會是錯的,因為它不滿足第二個敘述。且

如果 X可以在程式中不同的點有不同的值,而某地方出錯了,那麼,到底是

那個 X的值造成錯誤?到底是那個點得到這個出錯的 X值?這些問題都會變

得很難以回答。

在 Erlang中,只要設定之後,變數值就無法改變。這可以幫助除錯的簡化。

想瞭解為何會如此,我們必須好好地思考,什麼是錯誤,且錯誤要如何被

發現。

Page 19: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 22

沒有副作用,這意味著我們可以將程式平行化處理

可以被修改的記憶體,術語稱為「可變狀態」(mutable state)。Erlang

是一個函數式(functional)編程語言,且具有「非可變狀態」。

本書後面,我們會看看如何編寫多核心 CPU 的程式。當面對多核心編

程時,「非可變狀態」所帶來的好處,是相當大的。

如果你使用一個傳統的編程語言,像是 C或 Java來處理多核心 CPU,

那麼你需要對付記憶體共享的問題。為了不要敗壞(corrupt)共享的記

憶體,使用記憶體時,必須將記憶體上鎖。像 C或 Java這種存取共享

記憶體的程式,在處理共享記憶體時,要很小心,不然會當機。

在 Erlang中,沒有易變狀態,沒有共享記憶體,不需要上鎖。這使得程

式的平行化變得相當容易。

你會發現程式不正確,可能是因為某個值和你預期的不同。如果是這樣,那

麼你必須當程式維持在不正確值時,發現這件事。如果此變數改變值多次,

且分佈在許多時間點,那麼找出確切「值不正確」的地方會相當困難。

對於 Erlang來說,沒有這樣的問題。變數只能被設定一次,然後就再也不會

被改變了。所以一旦我們知道哪個變數是不正確的,我們就可以立刻推斷出

此變數被繫結的地方,一定是發生錯誤的地方。

此時,你可能會懷疑,程式不用到變數,怎麼可能?要如何表示像「X = X +

1」這樣的式子?答案很簡單,創造一個新的變數,其名稱尚未被使用過(比

方說 X1),然後就可以寫出「X1 = X + 1」。

2.7 浮點變數值

讓我們試圖做一些浮點算術:

1> 5/3.

1.66667

2> 4/2.

2.00000

3> 5 div 3.

1

Page 20: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

23 Chapter 2 開始學習

4> 5 rem 3.

2

5> 4 div 2.

2

6> Pi = 3.14159.

3.14159

7> R = 5.

5

8> Pi * R * R.

78.5397

不要混淆了。在第 1行的最後是整數 3,不是小數 3,最後的點是表示式

的句號而不是小數點。如果我要寫浮點數,會寫成 3.0。

「/」一定會傳出浮點數;因此 4/2得到 2.0000(在 shell中)。「N div M」

且「N rem M」分別是用來做整數的除法與求餘數;因此「5 div 3」是 1,

而「5 rem 3」是 2。

浮點數必須包含一個小數點,且小數點之後一定要有至少一個十進位數字。

當你用「/」將兩個整數相除,結果會自動轉成浮點數。

2.8 原子

在 Erlang,原子(atom)用來表示不同的非數字常數值。

如果你習慣 Java或 C的列舉型別,那麼其實你已經在使用非常類似於 atom

的東西了,儘管你不見得有意識到這一點。

C編程員都很熟悉,用符號常數可以讓程式容易閱讀理解。通常 C程式會在

一個引入檔(include file)內定義一些全域(global)常數,由許多常數定義

所組成;比方說,glob.h可能包含了:

#define OP_READ 1

#define OP_WRITE 2

#define OP_SEEK 3

...

#define RET_SUCCESS 223

...

Page 21: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 24

通常 C的程式碼用下面的方法來使用這些符號常數:

#include "glob.h"

int ret;

ret = file_operation(OP_READ, buff);

if ( ret == RET_SUCCESS ) { ... }

在 C程式碼中,這些常數的值並不是很有趣;唯一有趣的地方,是因為它們

都不一樣,且可以比較是否相等。

此程式的 Erlang對等版本看起來是這樣的:

Ret = file_operation(op_read, Buff),

if

Ret == ret_success ->

...

在 Erlang中,原子是全域的,且不需要使用巨集定義或引入檔案,就可以

辦到。

假設你想要寫出一個程式,處理星期幾。你要如何在 Erlang中表達星期的某

一天,當然你可以使用原子 monday、tuesday…。

原子是以小寫字母開始,後面跟著一序列的英數(alphanumeric)字元,或

底線(_),或@符號7。比方說:red、december、cat、meters、yards、

joe@somehost、a_long_name。

原子也可以用單引號包圍。使用括引號的格式,我們可以建立出「一開始是

大寫字母」的原子,或者「包含非英數字元」的原子。比方說:'Monday'、

'Tuesday'、'+'、'*'、'an atom with spaces'。你甚至可以將不需要括引

號的原子也括上引號,所以'a'的意義和 a完全一樣。

原子的值就是此原子。所以如果你把一個原子當作命令,Erlang Shell會印出

該原子的值:

1> hello.

hello

7 你可能會發現英文句號(.)也可以用來當作原子,但 Erlang不建議你這麼做。

Page 22: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

25 Chapter 2 開始學習

討論原子的值就和討論整數的值一樣,似乎有一點怪。但是因為 Erlang是一

個函數式(functional)編程語言,每個表示式都會有一個值,整數和原子當

然也是一種「表示式」,簡單到極致的表示式。

2.9 值組

假設你想要將固定數目的項目組成單一個值,那麼你就需要用到 tuple(值

組)。你可以用大括號建立一個值組,其內的項目用英文逗號隔開。比方說,

如果你想要表示某人的名字和身高,你可以寫成{joe, 1.82}。這就是一個

值組,包含一個原子和一個浮點數。

值組類似於 C的結構(struct),差別在於值組是匿名的(anonymous)。在

C中,型別 point的變數 P可以宣告如下:

struct point {

int x;

int y;

} P;

你利用點(.)運算子存取 C結構的欄位。要設定 point 中 x 與 y 的值,就

這麼寫:

P.x = 10; P.y = 45;

Erlang不具有型別宣告,所以想建立一個 point,這麼寫就可以了:

P = {10, 45}

這建立了一個值組(tuple),且將它的值繫結到變數 P。不同於 C,值組的

欄位不具有名稱,因為值組本身只包含兩個整數,我們必須記得它們各自的

用途。想記得一個值組的用途,習慣上會在值組內第一個位置插入一個原

子,用來描述該值組是什麼。所以我們會寫出{point, 10, 45},而非{10,

45},這使得程式更好理解一些8。

8 這種貼標籤的方式不是 Erlang強制的作法,而是普遍推薦的編程風格。

Page 23: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 26

值組可以套疊(nest)。假設我們想要展現某人的一些資料(名字、身高、

足長、眼睛的顏色)。我們可以這麼做:

1> Person = {person,

{name, joe},

{height, 1.82},

{footsize, 42},

{eyecolour, brown}}.

注意,我們這裡使用原子來識別欄位(名字與眼睛顏色),同時當欄位的值。

建立值組

當宣告時,值組會自動建立;當不需要時,值組會自動毀滅。Erlang使用垃

圾收集器,來回收不用的記憶體,所以我們可以不用擔心記憶體配置的問題。

如果你使用一個變數來建立一個新的值組,那麼此新值組將會共用「此變數

所參考到的資料結構」的值,請看範例:

2> F = {firstName, joe}.

{firstName,joe}

3> L = {lastName, armstrong}.

{lastName,armstrong}

4> P = {person, F, L}.

{person,{firstName,joe},{lastName,armstrong}}

如果你試圖建立一個資料結構具有未定義的變數,那麼你就會得到錯誤訊

息。所以在下一行,如果你試圖使用尚未定義的變數 Q,會得到錯誤:

5> {true, Q, 23, Costs}.

** 1: variable 'Q' is unbound **

這表示變數 Q是沒有定義的。譯者註

從值組中取出值

稍早,我們說過=看起來像是指定運算子,但其實是模式比對運算子。你可

能懷疑為何我們要這樣賣弄學問,這是因為「模式比對」是 Erlang的重要基

譯者註 提醒你,原子開頭字母小寫,變數開頭字母大寫。這是 Erlang強制的語法。

Page 24: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

27 Chapter 2 開始學習

礎,且用在許多不同的任務上:「從資料結構中取出值」會用到它;「函數

內的流程控制」會用到它;當你送出訊息到一個行程(process)中,選取要

處理的訊息,也會用到它。

如果我們想要從一個值組中取出一些值,我們會使用=模式比對運算子。

讓我們看看代表 point的值組:

1> Point = {point, 10, 45}.

{point, 10, 45}.

假設我們想要取出 Point的欄位,分別放進 X與 Y,我們的作法是:

2> {point, X, Y} = Point.

{point,10,45}

3> X.

10

4> Y.

45

在命令 2中,X 被繫結到 10,Y 被繫結到 45。此表示式「Lhs = Rhs」的值

被定義為 Rhs,所以 Shell會印出{point,10,45}。

如你所見,等號兩邊的值組必須具有相同個數的元素,且對應的元素必須繫

結到相同的值。

現在假設你輸入一些東西,像是:

5> {point, C, C} = Point.

=ERROR REPORT==== 28-Oct-2006::17:17:00 ===

Error in process <0.32.0> with exit value:

{{badmatch,{point,10,45}},[{erl_eval,expr,3}]}

發生什麼事了?模式{point, C, C}和{point, 10, 45}無法符合比對,因為

C不可以同時為 10和 45。因此,模式比對失敗9,且系統印出錯誤訊息。

9 熟悉 Prolog的讀者請注意:Erlang認為比對不合就是失敗,且不沿原路返回。

Page 25: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 28

如果你具有一個複雜的值組,那麼你從值組中取出值的作法,是寫出一個模

式,其形狀(也就是結構)和值組一致,且在你想取出值的地方使用未繫結

的變數10。

想解說這一點,我們先定義一個變數 Person,它包含一個複雜的資料結構:

1> Person={person,{name,{first,joe},{last,armstrong}},{footsize,42}}.

{person,{name,{first,joe},{last,armstrong}},{footsize,42}}

現在我們寫一個模式來取出此 person的第一個名字:

2> {_,{_,{_,Who},_},_} = Person.

{person,{name,{first,joe},{last,armstrong}},{footsize,42}}

最後,我們印出 Who的值:

3> Who.

joe

請注意,在前面的範例中,遇到我們不感興趣的變數,我們寫一個「_」,

此符號稱為匿名變數(anonymous variable)。和正常變數不同的是,同一個

模式中_出現多次,不必繫結到相同的值。

2.10 清單

我們使用清單(list)來儲存變動個數的東西,例如:你的採購項目、星球的

名稱、質數函數會傳出的值…。

我們利用中括號,就可以建立清單,括號內的元素用逗號隔開。如下面的購

物清單所示:

1> ThingsToBuy = [{apples,10},{pears,6},{milk,3}].

[{apples,10},{pears,6},{milk,3}]

清單的個別元素可以是任何型別,比方說下面的例子:

2> [1+7,hello,2-2,{cost, apple, 30-20},3].

[8,hello,0,{cost,apple,10},3]

10 使用模式比對取出變數的方法,稱為 unification(統一化)。在許多函數式與邏輯式編程語言中,都看得到 unification。

Page 26: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

29 Chapter 2 開始學習

術語

我們稱清單的第一個元素是清單的頭(head)。想像從清單中移除頭,剩下

的部分稱為清單的尾(tail)。比方說,如果我們具有一個清單[1,2,3,4,5],

然後清單的頭是整數 1,則清單的尾是[2,3,4,5]。注意清單的頭可以是任何

東西,但是尾一定也是一個清單。

取用清單的頭,效率相當高,所以幾乎所有的清單處理函數都是先取出清單

的頭,然後對它做一些事,然後才處理清單的尾。

定義清單

如果 T 是一個清單,那麼[H|T]也是一個清單11,其頭是 H,且尾是 T。垂直

線「|」用來區隔頭和尾。而[ ]是一個空的清單。

當我們使用[...|T]語法建立清單時,我們應該確定 T 是一個清單。如果是

的話,那麼此新的清單就是『具有適當的格式』的。如果 T不是一個清單,

則新的清單會被認為『格式不適當』。大多數的程式庫函數假定清單是格式

適當的,且無法處理格式不適當的清單。

我們可以寫[E1,E2,..,En|T],加入更多元素到 T的頭,比方說:

3> ThingsToBuy1 = [{oranges,4},{newspaper,1}|ThingsToBuy].

[{oranges,4},{newspaper,1},{apples,10},{pears,6},{milk,3}]

從清單中取出元素

我們可以利用模式比對運算子,從清單中取出元素。如果我們具有非空清單

L,那麼表示式「[X|Y] = L」(其中 X與 Y是未繫結變數)會將清單的頭放

到 X,將清單的尾放到 Y。

11 LISP編程員請注意:[H|T]是一個 CONS cell,具有 CAR H與 CDR T。在模式中,此語法會將 CAR與 CDR拆開。在表示式中,會建構出一個 CONS cell。

Page 27: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 30

我們已經到商店了,我們具有一個購買清單 ThingsToBuy1,第一件事,就是

打開清單,分成頭與尾兩部份:

4> [Buy1|ThingsToBuy2] = ThingsToBuy1.

[{oranges,4},{newspaper,1},{apples,10},{pears,6},{milk,3}]

成功地繫結起:

Buy1 {oranges,4}

並繫結起:

ThingsToBuy2 [{newspaper,1}, {apples,10}, {pears,6}, {milk,3}]

我們要先買 oranges,然後就可以取出下面的項目:

5> [Buy2,Buy3|ThingsToBuy3] = ThingsToBuy2.

{newspaper,1},{apples,10},{pears,6},{milk,3}]

成功地讓 Buy2 {newspaper,1},且讓 Buy3 {apples,10},還有

ThingsToBuy3 [{pears,6},{milk,3}]。

2.11 字串

嚴格說來,Erlang沒有字串,字串其實只是許多整數構成的一個清單。字串

放在一對雙引號內,像下面這樣:

1> Name = "Hello".

"Hello"

注意:在某些編程語言中,字串可以用單引號或雙引號當括號,但是在 Erlang

中,我們只能使用雙引號。

"Hello"只是整數清單的速寫方式,每個整數是個別字元的編碼。

當 Shell 要印出此清單的值,會將它當作字串印出。但是只有當清單中的全

部的整數都是可印字元時譯者註

,才會如此。請看範例:

譯者註 Erlang只有考慮到 ASCII的可見字元,並不把其他國家的語言編碼考慮在內。

Page 28: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

31 Chapter 2 開始學習

2> [1,2,3].

[1,2,3]

3> [83,117,114,112,114,105,115,101].

"Surprise"

4> [1,83,117,114,112,114,105,115,101].

[1,83,117,114,112,114,105,115,101].

表示式 2中,清單[1,2,3]會被直接印出,沒有轉成字串。這是因為 1、2、3

都是不可印字元。

表示式 3的所有項目都是可印字元,所以會印成字串。

表示式 4就和表示式 3一樣,除了清單一開始是 1之外。但是 1是不可印字

元,所以會直接印出,不轉換成字串。

我們不需要知道,哪一個整數代表某個特定的字元。我們可以使用『錢號語

法』來達到此目的。比方說,$a是一個整數,代表一個字元 a。

5> I = $s.

115

6> [I-32,$u,$r,$p,$r,$i,$s,$e].

"Surprise"

字串使用的字元集

字串使用的字元是屬於 Latin-1(ISO-8859-1)字元編碼。比方說,字串包含

瑞典名字「Håkan」,會被編碼成[72,229,107,97,110]。

注意:如果你輸入[72,229,107,97,110]當作 Shell表示式譯者註

,你可能不會

得到你所期望的:

1> [72,229,107,97,110].

"H\345kan"

發生什麼事?這其實和 Erlang 沒有關係,而是和你的終端機(terminal)設

定有關係。

譯者註 在正體中文環境中,你可能會看到的是「"H嶡 an"」而非「"H\345kan"」。

Page 29: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

Erlang程式設計 32

Erlang 只知道,字串是一個由整數構成的清單,這些整數代表某種編碼

(encoding)。如果剛好是可印的 Latin-1碼,就會正確的地顯示出來(當然

也必須終端機設定正確才行)。

2.12 再談模式比對

想要讓本章圓滿結束,我們必須再次回到模式比對。

下面的表格具有一些模式與 term的範例12。表格的第三個欄位(標示為結果)

顯示是否模式符合該 term,且如果是的話,繫結的變數為何。看看這些範例,

確定你真的瞭解一切:

模式 term 結果

{X,abc} {123,abc} 成功 X 123

{X,Y,Z} {222,def,"cat"} 成功 X 222, Y def, Z "cat"

{X,Y} {333,ghi,"cat"} 失敗。值組具有不同形狀(結構)

X true 成功 X true

{X,Y,X} {{abc,12},42,{abc,12}} 成功 X {abc,12}, Y 42

{X,Y,X} {{abc,12},42,true} 失敗。X無法同時為{abc,12}與 true

[H|T] [1,2,3,4,5] 成功 H 1, T [2,3,4,5]

[H|T] "cat" 成功 H 99, T "at"

[A,B,C|T] [a,b,c,d,e,f] 成功 A a, B b, C c, T [d,e,f]

如果上面的範例有你不確定者,試著在 Shell輸入「模式 = term」表示式,

看看會發生什麼事。

比方說:

1> {X, abc} = {123, abc}.

{123,abc}.

2> X.

123

12 term是指 Erlang資料結構。

Page 30: Erlang:世界是平行的!epaper.gotop.com.tw/PDFSample/AXP012800.pdf · y 你會開始學習程式庫(Erlang的程式庫稱為模組)。我所寫出來的程 式,大部分都可以只用到五個模組:lists、io、file、dict、gen_tcp;

33 Chapter 2 開始學習

3> f().

ok

4> {X,Y,Z} = {222,def,"cat"}.

{222,def,"cat"}.

5> X.

222

6> Y.

def

...

注意:f()用來告訴 Shell,忘掉它所具有的任何繫結。在此命令之後,所有

的變數都會變成未繫結,所以第 4行的 X和第 1、2行的 X沒有關係了。

現在我們已經習慣基本的資料型別,且具有單一指定與模式比對的知識,所

以我們可以加快步伐,看看如何定義函數與模組,這是下面兩章的內容。