erlang入門筆記
2008-06-20
版權聲明
:轉載時請以超鏈接形式標明文章原始出處和作者信息及
本聲明
http://cbkid.blogbus.com/logs/23258709.html
1.1.2 ? 其它方面
文章省略了以下幾個方面:
● 參考
● 本地錯誤處理 (cache/throw)
● 單向連接 ( 顯示器 )
● 二進制數據處理
● 列表相關
● 與外界如何通信,以及 / 或者 port 其它語言開發的軟件。當然,有一些向導中會單
獨講解這個問題。 < 互操作向導 >
● 涉及到的極少數 Erlang 庫 ( 如,文件處理 )
● 關于 OTP 的問題完全被跳過,關于 Mnesia 數據庫的信息在結論中也被省略。
● ? Erlang 中的哈殺表。
● 運行時改變代碼。
1.2.2 ? 模塊和函數
Erlang 程序寫在文件中。每個文件都包含一個 Erlang 模塊 (Module) 。
文件名 tut.erl 這一點很重要,同時需要確定文件與 erl 在同一個目錄下 .
{ok,tut} 告訴你編譯成功。如果它提示 "error" ,你可能在輸入文本的時候出錯,并而錯
誤信息可能會給你一些有關于如何糾正錯誤的想法,依此你可以改變你的代碼,重新再試。
現在讓我們運行這個程序。
4> tut:double(10).
Erlang 程序寫在文件中。每個文件都包含一個
Erlang 模塊 (Module) 。在文件中的第一行,就告訴我們模塊的名稱 (See the
chapter "Modules" in the Erlang Reference Manual) 。
-module(tut).
這告訴我們模塊的名稱是 tut 。注意本行結尾的 "." 。存放模塊代碼的文件的名字,也必須
和模塊同名但以 ".erl" 做為擴展名。。當我們使用另
一個模塊的函數,我們使用語法,模塊名 : 函數名 ( 參數 ) 。所以
4> tut:double(10).
意味著我們調用 tut 模塊中的 double 函數,并使用 "10" 做為參數。
-export([double/1]).
說明 tut 模塊包含一個名稱為 double 的函數,并且帶有一個參數 ( 在我們的例子中為 X)
并而這個函數可以在 tut 模塊以外被調用。
一個數字的階乘 ( 如: 4 的階乘是 4*3*2*1) 。在名為
tut1.erl 中輸入下面的代碼。
-module(tut1).
-export([fac/1]).
fac(1) ->
1;
fac(N) ->
N * fac(N - 1).
第一部分:
fac(1) ->
1;
說明 1 的階乘是 1 。注意我們以一個 ";" 結束這一部分,這說明這個函數沒有結束。第二部
分:
fac(N) ->
N * fac(N - 1).
說明 N 的階乘是, N 與 N - 1 的階乘的乘積 (N! = N * (N - 1)!) 。注意這部分
以 "." 結束,以說明本函數沒有其它部分了。
-export([fac/1, mult/2]).
mult(X, Y) ->
X * Y.
注意,我們同時也擴展了 -export 行,并給于它關于另一個帶有兩個參數的函數信息。
變量首字母必須是大寫 .
1.2.3 ? 元子 (Atoms)
元子是在 Erlang 中的另一個數據類型。元子以小寫字母開頭 .
元子只是一個簡單的名字,其它什么都不是。他們不像變量可以帶有一個值。
1.2.4 ? 元組
元組以 "{" 和 "}" 括起來的。不過元組可以有很多部分,我們想要多就都可以。
例如:
{inch, X} {moscow, {c, -10}}
一組元組有一個固定的大小。我們稱元組中的東西為‘元素’。所以在元組 {moscow,{c,-
10}} 中,元素 1 是 moscow ,元素 2 是 {c, -10} 。
1.2.5 ? 列表
列表在 Erlang 中被括在 "[" 和 "]" 里。
Erlang 可允許分成多行,不過,不可以在元子或整數中間的某部分來分。
一個很有用的遍歷列表的方法是使用 "|" 。
[First |TheRest]= [1,2,3,4,5]
我們使用 | 來分隔列表中的第一個元素和后續的元素。
[E1, E2 | R] = [1,2,3,4,5,6,7]
這里的 | 是用來得到前兩個元素。當然,如果我們試著從列表中得到比列表中定義的元素個數更多的元素的話,我們會得到一個錯誤。
我們通常所說的使用元組,在其它的編程語言中,我們可能會叫做“記錄”或“結構體”。我們使用列表 ( 我們在其它編程語言中所什么的“鏈表” ) 。
Erlang 并沒有字符串類型,取而代之我們可以提供一個由 ASCII 字符組成的列表。
1.2.6 ? 標準模塊及用戶手冊
io:format/2 函數自身返回一個元子 ok ,注釋以 % 開始,直到本行結束。同時也要注意 -export([format_temps/1]). 一行只包含 format_temps/1 函數,另一個函數是局部 (local) 函數,他們無法被 tut5 模塊以外的東西訪問。
1.2.9 ? 變量的匹配、守衛和作用域
首先注意我們這里有兩個同名的函數 list_max 。雖然每個都帶有不同數據的參數。在 Erlang 這些都會被認為是完全不同的函數。
我們需要通過“名稱 / 參數數量”區分這些我們 寫的函數,比如在這里是 list_max/1 和 list_max/2 。
在 -> 之前的特定字符,表示--我們只會在這個特定的條件滿足的時候, 才會執行函數的這個部分。我們叫這種類型的測試,叫守衛 (guard) 。如果守衛不是‘真’ ? ( 我們稱它為守衛失敗 ) ,我們會嘗試執行函數的下一個部分。這種情況下如果 Head 不大于 Result_so_far 的話,它必定是小于等于它,所以我們在下一部分中,不需要再使用守衛。一些守衛中常見的操作符有 < ? 小于, > ? 大于, == 等于, >= ? 大于等于, =< ? 小于等于, /= ? 不等于。
Result_so_far 給賦了很多值。那是因為,我們每次調用 list_max/2 的時候,我 們建立了一個新的作用域, Result_so_far 在這個作用域中,都會被認為是完全不同的變 量。
另一個建立和給一個變量賦值的方法是使用 ? = 。如果我寫 M = 5 ,那么一個名為 M 的變量 會被創建,并賦于 5 這個值。如果在同一下作用域下,當我寫 M = 6 ,它就會發生錯誤。
匹配操作符在通過一組元素創建新變量的時候很有用。
{X, Y} = {paris, {f, 28}}
這里我們看到 X 的值被賦于 paris ,而 Y 是 {f,28} 。
1.2.10 ? 更多關于列表
| 也可以用來在列表的頭部添加元素:
模塊 lists 包含了很多函數用于維護列表,例如反轉列表。所以在我們寫一個列表函數之 前,最好先看一下是否在庫中已經存在了這樣一個函數。
1.2.11 If 和 Case
if
Condition 1 ->
Action 1;
Condition 2 ->
Action 2;
Condition 3 ->
Action 3;
Condition 4 ->
Action 4
end
注意,在結尾沒有 ";" !條件 (Conditions) 和守衛一樣,測試成功或是失敗。 Erlang 會 從最頂上開始向下,直到找到一個成功的條件為止,并執行行條件下面的動作,并且乎略掉 其后面它的條件。如果沒有條件匹配,會發生一個運行時 (run-time) 錯誤。元子 true 在條件中表示真,它常常被放在條件層,表示如果沒有匹配的條件的話,應該做什么動作。
convert_length(Length) ->
case Length of
{centimeter, X} ->
{inch, X / 2.54};
{inch, Y} ->
{centimeter, Y * 2.54}
end.
注意, case 和 if 都有返回值,即,在上面的例子中 case 返回 {inch,X/2.54} 或 ? {centimeter,Y*2.54} 。 case 的行為也可以使用守衛來代替。
1.2.12 ? 內建函數 (BIFs)
內建函數 (BIFs) 是一個因為某原因被內嵌在 Erlang 虛擬機中的函數。 BIFs 經常是一些 在 Erlang 上無法實現的功能,或在 Erlang 下實現起來效率不高的東西。有些 BIFs 可以 直接使用函數的名字,他們默認是屬于 erlang 模塊下的函數。比如 trunc 函數,和 ? erlang:trunc 調用是等同的。
只有很少一部分的內建函數,可以于用守衛,你也無 法在守衛中使用你自己定義的函數。
(see the chapter "Guard Sequences"(http://www.erlang.org/doc/reference_manual/expressions.html) in the Erlang Reference Manual) 。
1.2.13 ? 復雜函數
convert_to_c 函數,和以前定義的一樣,不過我們把它做為一個帶入函數來用: ? lists:map(fun convert_to_c/1, List)
當我們使用一個要別處定義的 fun 函數的話,我們可以能過函數名 / 參數個數,來區別。
在 sort 函數中,我們使用的 fun 函數 : fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,
這里,我們介紹匿名變量” _” 。這是一個簡單化的,用于一個將得到值的變量,并且我們還 需要丟棄這個值的情況下。它可以用在任何合適的場合中,不只在 fun 中使用。 Temp1 < Temp2 返回 true 如果 Temp1 小于 Temp2 。
1.3 ? 并行編程
1.3.1 ? 進程
并發,我們的意思是一種可以處理掌握若干線程同時執行這樣的程序。
”進程”經常指線程間沒有共享數據,”線程”指他們之間,有共享的數據或共享內存區
Erlang 內建函數 spawn 被用于建立一個新的進程: spawn( 模塊 , ? 導出的函數 , ? 參數列 表 ) 。
spawn 返回一個進程 ID ,或 pid ,即一個進程的唯一標識。所以 <0.63.0> 是 spawn 函數 的 pid ,即進程 ID 。
1.3.2 ? 信息傳遞
receive 構造用于允許進程等待來自其它進程的消息。它的格式是:
receive
pattern1 ->
actions1;
pattern2 ->
actions2;
....
patternN
actionsN
end.
注意,在結尾沒有” ;” 。
在 Erlang 進程間的消息是一個有效的 Erlang 字串,即,它們可以是列表、元組、整數、 元子、 pid 等等。
每個進程對于它收到的消息,有一個自己的輸入隊列。新的消息會放在隊列的尾部。當一個 進程執行了一個 receive ,隊列中第一個與匹配條件一致的消息就會從隊列中移除,并匹 配的動作會執行。
如果第一個匹配試沒有被匹配的話,第二個就會被檢驗,如果與隊列中移除的條件匹配了的 話,相對于第二個式子中的運作會被執行。第三個也依次類推。如果沒有匹配的話,那么第 一個消息就會被放回隊列中,并用第二個消息來做對應的操作。如果第二個消息匹配的話, 相應的動作就會被執行,并把第二個消息從隊列中移除 ( 并保留第一個消息和其它的消息在 隊列中 ) 。如果第二個消息也不匹配,我們會試著用第三個消息,直到達到隊列尾。如果到 達到隊列尾部,進程就會阻塞 ( 中止執行 ) 并等待,直到一個新的消息被收到,上面的過程 重復。
注意,” !” 是如何發送消息的,以及” !” 的句法:
Pid ! Message
即, Message( 任何東西 ) 被發到給了標識為 Pid 的進程。
Pong_PID ! {ping, self()},
self() 返回當前自在運行的進程 ID ,在這里是” ping” 的進程 ID 。
1.3.3 ? 進程名稱注冊
有些時候進程 可能需要知道每一個與它不相關的,啟動的進程的標識。
這是由 register 這個內建函數完成的:
register(some_atom, Pid)
1.3.4 ? 分布式編程
分布式 Erlang 的實現,提供了一個基本 的安全機制以防止來自其它計算機的非授權的訪問 (*manual*) 。
Erlang 系統要想互相通 信,必需要相同的 magic cookie 。最簡單的獲取它的方法是在你的每臺需要 Erlang 通 信的機器中的 home 文件夾中建立一個 .erlang.cookie 的文件 ( 在 Windows 系統中, ? home 文件夾由 $HOME 環境變量指定 - 你可能需要首先設定它。在 Linux 或 Unix 系統 中,你可以忽略這一步,只需要簡單的在你用戶 home 文件夾中,建立 .erlang.cookie ? 就可以了 ) 。 .erlang.cookie 文件中需要包含相同的元子字串。如 Linux 或 Unix 系統
中:
$ cd
$ cat > .erlang.cookie
this_is_very_secret
$ chmod 400 .erlang.cookie
上面的 chmod 使 .erlang.cookie 文件只可以被它的所有者訪問。這是必需的。
當你啟動一個需要與其它機器上的 Erlang 系統交互的一個 Erlang 系統時,你必須給它一 個名字,如:
erl -sname my_name
如果你希望體驗一下分布式 Erlang ,可是你 只有一機計算機的話,你只需要啟動兩個獨立的 Erlang 系統在同一臺機器上,并給他們不 同的名字就可以了。每一個運行于計算機上的 Erlang 系統,被稱為一個 Erlang 節點。
( 注意: erl -sname ? 假設所有節點都在同一個 IP 域下,如果我們想要使用其它 IP 域上
的節點,我們使用 -name 代替,并而需要給出完全的 IP 地址
Erlang 的 pid 已經包 含它了有關于進程運行的相關信息,所以你只要知道 pid ,” !” 就可以用于發送消息,無論 目的地是在同一個節點上,還是其它節點上。
不同的是,我們如何把消息發送到另一個節點上注冊的進程中: ? {pong, Pong_Node} ! {ping, self()}, ? 我們使用元組 {regiester_name, node_name} 代替 注冊名。
一個 Erlang 進程理論上將一直運行,直到收到不需要的信息。之所以我說“理論上”因為 ? Erlang 系統與活動進程運享 CPU 時間。
當沒有事可做的時候,一個進程會終止,即,最后一個函數被調用,并返回一個值,而且不 再調用其它函數了。另一個中止進程的方法是使用 exit/1 。 exit/1 的參數有特定的含意, 我們后面再說。
內建函數 whereis(RegisteredName) 檢測如果一個注冊的進程名稱叫 ? RegisteredName 存在,如果存在,返回 pid 。否則返回 undefined 。
1.4 健壯性 (Robustness ? 魯棒性 )
-
超時 (Timeouts)
超時設 置 在 這段代碼 中:
pong() ->
receive
{ ping , ? Ping_PID } ? ->
io:format( " Pong received ping ~ n ", ? []) ,
Ping_PID ? ! ? pong ,
pong()
after 5000 ->
io:format( " Pong timed out ~ n ", ? [])
end.
當 我 們 進入 receive 代碼段 時, 啟 動 了 超時 機 制 (after 5000) ? 如果接 受 到 了消 息 { ping , Ping_PID } 超時將 被 取 消 ,如果 沒 有 收 到 該消 息, 在 5000 毫秒 之后,超時 代碼段 的 操 作將 被 執 行。 after 語句必須 是 receive 代碼段 的最后一個匹配 條 件。 ? 就 是 說 , 讓 receive 代碼段 中的其匹配 定 義優 先 處理。 ? 當然 ,我 們也 可以使用一個 返 回 一個 整 數的函數 來 作 為 超時的設 定值 : after pong_timeout() ? →
-
錯誤處理
一個進程如果使用 exit(normal) 退 出 或 者 運 行完所有的 代碼然 后 退 出稱 為 正 常 (normal) 退 出 .
一個進程如果發 生了運 行時錯誤 ( 例如 ? 除 以 0 ,錯誤的匹配, 試圖調 用一個不 存 在的函數 等 . 將 因 為 錯誤而 結束 , ? 也就 是 異 常 (abnormal) 退 出。
進程 通過調 用 link(Other_Pid) ( * manual * ) 來 建 立自 身 和 Other_Pid 進程的 雙向 鏈 接。 ? 當 一個進程中 止 的時 候 ,將 給每 一個 與 它建 立了 接的進程發 送 一個信 號 (signal) 。 這 個信 號包含了 發 送 者的 pid 和進程 結束 的原 因 。
你可以將一個 事 務 (transaction) 所 涉 及到的所有進程 連 接在一 起 , 如果其中的一個進程 異 常退 出,所有的 該事 務 中的進程將 被 全部 殺 死 。
內 嵌 函數 spawn_link 在完成 spawn 函數 功 能的 同 時建 立了 一個 與 新 創 建的進程的 鏈 接
我 們 可以 修 改 一個進程接 收 到 異 常退 出信 號 時的缺 省退 出行 為 , ? 這 時所有的信 號都 將 被轉換 成一個 格
發表評論
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

評論