2014/2/9

erlang basics - module, variable, pattern matching

module

module 是程式碼的容器,每個 module 的名字都是 global 唯一的 atom。

呼叫函數

local 呼叫是指呼叫同一個 module 裡的函數
remote 呼叫是指呼叫其他 module 裡的函數

以 module:fun 的方式呼叫函數

erlang 的函數數量稱為 arity 元數,函數的全名必須要包含 arity,例如 reverse/1、lists:reverse/1、lists:reverse/2

18> lists:reverse([3,2,1]).
[1,2,3]
19> lists:reverse([5,6,7],[3,2,1]).
[7,6,5,3,2,1]

BIF: built-in function

erlang 內建一套標準函式庫,散布在各個 module 中,例如: erlang、lists、dict、array。

BIF 會自動被 include,不需要 include_lib,例如 self() 其實就是 erlang:self(),因為自動引用,所以可以省略 erlang:

自建 module

建立 test_module.erl 檔案

%% test_module.erl
-module(test_module).

-export[hello/0].
-export[hello/1].

hello(From) ->
        io:format( "~s:Hello world~n", [From] ).

hello() ->
        hello("").

可在 linux shell 裡面直接以 erlc 編譯,會產生 test_module.beam 檔案,可使用 -o 指定 .beam 輸出的目錄。

> erlc test_module.erl
> erlc -o ebin test_module.erl

在 erl shell 裡面,erlang 會直接嘗試載入 test_module,但如果有修改 test_module.erl,也可以直接以 c(test_module) 編譯並載入更新後的 test_module

1> test_module:hello().
 Hello world
ok
2> test_module:hello("test").
test:Hello world
ok
3> c(test_module).
{ok,test_module}
4> test_module:hello("test").
test:Hello world
ok
5> test_module:hello().
:Hello world
ok

code:get_path(). 可將所有 shell 會自動搜尋的路徑列印出來

4> code:get_path().
[".","/usr/local/lib/erlang/lib/kernel-2.16.4/ebin",
 "/usr/local/lib/erlang/lib/stdlib-1.19.4/ebin",
 "/usr/local/lib/erlang/lib/xmerl-1.3.5/ebin",
 "/usr/local/lib/erlang/lib/wx-1.1.1/ebin",
 "/usr/local/lib/erlang/lib/webtool-0.8.9.2/ebin",
 "/usr/local/lib/erlang/lib/typer-0.9.5/ebin",
 "/usr/local/lib/erlang/lib/tv-2.1.4.10/ebin",
 "/usr/local/lib/erlang/lib/tools-2.6.13/ebin",
 "/usr/local/lib/erlang/lib/toolbar-1.4.2.3/ebin",
 "/usr/local/lib/erlang/lib/test_server-3.6.4/ebin",
 "/usr/local/lib/erlang/lib/syntax_tools-1.6.12/ebin",
 "/usr/local/lib/erlang/lib/ssl-5.3.2/ebin",
 "/usr/local/lib/erlang/lib/ssh-3.0/ebin",
 "/usr/local/lib/erlang/lib/snmp-4.25/ebin",
 "/usr/local/lib/erlang/lib/sasl-2.3.4/ebin",
 "/usr/local/lib/erlang/lib/runtime_tools-1.8.13/ebin",
 "/usr/local/lib/erlang/lib/reltool-0.6.4.1/ebin",
 "/usr/local/lib/erlang/lib/public_key-0.21/ebin",
 "/usr/local/lib/erlang/lib/pman-2.7.1.4/ebin",
 "/usr/local/lib/erlang/lib/percept-0.8.8.2/ebin",
 "/usr/local/lib/erlang/lib/parsetools-2.0.10/ebin",
 "/usr/local/lib/erlang/lib/otp_mibs-1.0.8/ebin",
 "/usr/local/lib/erlang/lib/os_mon-2.2.14/ebin",
 "/usr/local/lib/erlang/lib/orber-3.6.26.1/ebin",
 "/usr/local/lib/erlang/lib/odbc-2.10.18/ebin",
 "/usr/local/lib/erlang/lib/observer-1.3.1.2/ebin",
 "/usr/local/lib/erlang/lib/mnesia-4.11/ebin",
 [...]|...]

variable

erlang 的變數必須以大寫的字母開頭,變數中每個單字的開頭,都要大寫(CamelCase)。

變數也可以用 _ 底線開頭,但以底線開頭的變數,就不會觸發沒有使用到這個變數的編譯器的警告,所有未被使用的變數,都會被優化掉。

Name
ShoeSize
ThisIsAVariable
_Name
_ShoeSize

底線變數有兩個用途

  1. 為我們不想用到的變數命名,例如寫 open(File, Mode) 比寫 open(File, ),程式更具有可讀性。
  2. 為了除錯的目的
    some_func(X) ->
     {P,Q} = some_other_func(X),
     io:format("Q= ~p~n", [Q]),
     P.
    如果將io:format 改為註解,編譯時,就會發生 Q 沒有使用的警告
    some_func(X) ->
     {P,Q} = some_other_func(X),
     %% io:format("Q= ~p~n", [Q]),
     P.
    因此我們把 Q 改為 _Q,就可以避免產生警告訊息
    some_func(X) ->
     {P,_Q} = some_other_func(X),
     io:format("_Q= ~p~n", [_Q]),
     P.

single assignment

當變數被指定數值時,該變數在程式作用的範圍中,就不會變動。

在 shell 中,只要這個 shell 還在運作,變數就不能異動,一旦異動,就會收到 pattern matching 的錯誤。但可以使用 f() 把變數的 binding 值忘記,然後就可以再次指定新值。

6> _Name=2.
2
7> _Name=3.
** exception error: no match of right hand side value 3
8> f(_Name).
ok
9> _Name=3.
3

pattern matching

這是 erlang 最重要的功能,pattern matching 是加強版的 value binding,有以下的用途:

  1. 選擇程式控制分支
  2. 完成變數binding
  3. 拆解資料結構

= 就是 pattern matching 的運算符號

11> {A,B,C}={2014,"NewYear",ok}.
{2014,"NewYear",ok}
12> A.
2014
13> C.
ok
14> B.
"NewYear"

15> {rect, Width, Height} = {rect, 100, 20}.
{rect,100,20}
16> Width.
100

{rect, Width, Height} 利用 rect 這個 atom 作為 tuple 資料的標籤,可在 pattern matching 時,馬上確認並檢查此資料來源的正確性。

don't care pattern: _

當我們有個 Users 的資料結構,裡面存有兩個 users,在這樣複雜的結構下,我們要怎麼確認第一個人符合我們設定條件呢?

用以下的方式,可確認第一個人 LastName 是不是 "Chen",同時取得那個人的 Firname 與 Age。

25> Users = [{person, [{name, "John", "Chen"}, {age, 30}, {tags, [math, male]}]}, {person, [{name, "James", "Chen"}, {age, 33}, {tags, [cs, female]}]} ].
[{person,[{name,"John","Chen"},{age,30},{tags,[math,male]}]},
 {person,[{name,"James","Chen"},
          {age,33},
          {tags,[cs,female]}]}]

26> [{person, [{name, Firstname, "Chen"}, {age, Age}, {tags, _}]} | _ ] = Users.
[{person,[{name,"John","Chen"},
          {age,30},
          {tags,[math,male]}]}]
27> Firstname.
"John"
28> Age.
30

可將 Rest 直接 bind 到後面三個元素的list。

6> [1,2,3 | Rest] = [1,2,3,4,5,6].
[1,2,3,4,5,6]
7> Rest.
[4,5,6]

可以套用在 strings 上,因為 string 也是一種 list。也可以直接使用 ++ 並進行 pattern matching。

8> [$h, $t, $t, $p, $: | Url]="http://www.maxkit.com.tw".
"http://www.maxkit.com.tw"
9> Url.
"//www.maxkit.com.tw"

12> "http://" ++ Rest1 = "http://www.maxkit.com.tw".
"http://www.maxkit.com.tw"
13> Rest1.
"www.maxkit.com.tw"

參考

Erlang and OTP in Action
Programming Erlang: Software for a Concurrent World