----------------小技巧-----------------------------
因為這一課開始,我們要使用Erlang文件操作,所以,我們期待啟動shell的時候,當前目錄最好是是我們的工作目錄,比如,打開erl shell環境,默認目錄就是“C:/Users/Kenneth/Documents/Erlang”。我們需要編輯C:/Program Files/erl5.9.1/usr(啟動shell后,執行pwd().來查看當前目錄是是什么)下的.erlang文件來達到這一目的。windows下創建.erlang是件頭大的事,圖形界面操作時,系統提示“必須要輸入文件名”,可以先創建一個文本文檔,然后在cmd環境下執行move xxx .erlang來改文件名。
.erlang文件內容如下:
io:format("according to the file .erlang in ~p~n", [element(2, file:get_cwd())]). %% Edit to the directory where you store your code io:format("cd "). c:cd("C:/Users/Kenneth/Documents/Erlang"). io:format("...~n"). io:format("OK! Now in:~p~n", [element(2, file:get_cwd())]).
這樣下次啟動shell,它會自動切換當前目錄為我們的工作目錄。
---------------------------------------------------
Erlang里面會把某一類的function放到一個單獨的文件中,然后這個整體被叫做Module;Erlang中所有的函數都必須放到Module里面。所以,除了前面兩課講到的基本數據類型,Module也是Erlang的基本元素之一。
1、調用函數時,我們必須以這樣的結構:Module:Function(Arguments)。除了BIFs。
2、BIF(Build In Function)使用時不需要指定module,這是因為虛擬機啟動時,默認加載了Erlang這一module里的所有函數進來。所有的arithmetic,logic,和boolean operator這些,也都是在Erlang 這一module中。例如:
> erlang:element(2,{a,b,c}). b > element(2,{a,b,c}). b > lists:seq(1,4). [1,2,3,4] > seq(1,4). ** exception error: undefined shell command seq/2
上面的代碼中,不管我們指不指明element函數屬於Erlang模塊,它都是可以被執行的;另外一面,seq屬於lists模塊,所以,如果不指定它屬於哪個module,系統就會拋出錯誤。
ps:不是所有Erlang中所有的函數都被加載,有一些不常用的,是沒有加載的。
3、除了需要一個單獨的文件,並且給他起一個別致的名字以外,Functions和Attributes這兩樣也是一個Module不可缺少的。Function不必多講,沒有Function,建立Module做什么?OK,那Attributes又是做什么用的?Attributes里面存放了描述Module自身用的一些metadata,比如它自己的名字,哪些函數是外部可以調用的,代碼作者啊等等這些。編譯器會將這些信息放到編譯完的代碼中去。這樣,即使是拿到編譯過的代碼,我們也可以查看這個module的基本信息,而不用再去翻source code。
attributes的定義方法是 -Name(Attribute)。
4、第一個Attributes必須是“-module(Name)”;這里的Name還必須和存放Module的file同名。這里的name便是我們按照MF(A)這樣調用函數時,必須要用到的module name。
5、然后另一個必不可少的Attributes是" -export([Function1/Arity, Function2/Arity,...,Function3/Arity])"。其中,Function是函數名稱,Arity是這個函數接受幾個參數。在Erlang中,相同名稱不同參數個數的函數是當做不同函數對待的,這類似OOP里面的重載。
6、還有一個attributes比較重要“-import(Module, [Function/Arity, ..., Function/Arity])”,其作用就是將module中的函數加載進來,然后,我們調用的時候就直接F(A)即可(就像虛擬機一開始對Erlang這一module做的一樣)。這里有個問題,很多人都不大贊成用import來完成工作。主要因為,本來在設計module的時候,只需要考慮function name在本module內唯一就好了,但是,現在如果從不同的module引入function,使用的時候,就得小心翼翼了。
7、define是另一個比較重要的attributes,使用方法是“-define(MACRO, some_value)”。這個和C中的define有點像。同樣的define也是常被用來定義一些常量和短的函數。Erlang的宏定義使用的時候,格式為“?MACRO”。
ps:?MODULE會被換成module name,as a atom;?FILE會被換成本文件的filename,as a string;?LINE會被換做所在行的行號。
ps:同樣的我們可以使用-ifdef(MACRO),-else和-endif等宏定義檢查語句,比如:
-ifdef(DEBUGMODE). -define(DEBUG(S), io:format("dbg: "++S)). -else. -define(DEBUG(S), ok). -endif.
如此,我們可以在后面的代碼中使用?DEBUG("entering xxx function!~n")這樣的語句來添加調試信息。這樣,如果我們如果在編譯的時候使用了DEBUGMODE開關,那么,調試信息將被打印出來;如果我們沒有使用DEBUGMODE開關,這句只是被替換為ok這一atom,單獨這樣一條語句的話,是什么也不會做。
8、函數的定義語法是Name(Args) -> Body(結束符)。Name必須是一個atom。Body可以是一條或者多條由逗號隔開的Erlang語句。(結束符)則是分號或者句號之一,我們在定義多個同名的函數時,每個函數之間使用“;”號隔開的,而所有函數定義實現都已經完成了之后,我們要在最后加一個“.”。
9、另外還有一個比較常見的attributes是:
-spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType."
這里,如果是同一module內,我們可以把“Module”字段略去不寫。
這一語句是用來定義函數規范的,如果我們定義了函數規范,那么我們就可以在運行前使用dialyzer來對程序進行靜態檢查,這樣就可以發現一些我們由於手抖造成的錯誤。
下面這句,意思是,我們定義了一個函數index(),它需要三個參數,第一個參數可以是任意的數據類型,第二個是正整數,第三個參數是一個list(這里的[any()]和lists()是等價的),而return的是一個非負數。
-spec( index(any(), pos_integer(), [any()]) -> non_neg_integer() ).
另外與spec相關的還有type,具體可以戳下面:
http://erlangdisplay.iteye.com/blog/404570
================分割線===========================
so,一個module就這樣建好了,一起來看看我們建的第一個module:
-module(helloworld). -export([greet_and_add_two/1]). add(A,B) -> A + B. %%% Shows greeting. %%% io:format/1 is the standard function used to output text. hello() -> io:format("Hi Erlang!~n"). greet_and_add_two(X) -> hello(), add(X,2).
然后我們嘗試編譯並使用我們module:
> cd("C:/Users/zhenxingluo/Documents/Erlang"). C:/Users/zhenxingluo/Documents/Erlang ok > c(helloworld). {ok,helloworld} > helloworld:greet_and_add_two(3). Hi Erlang! 5 > helloworld:module_info(). [{exports,[{greet_and_add_two,1}, {module_info,0}, {module_info,1}]}, {imports,[]}, {attributes,[{vsn,[251566447801502640269456451546098169641]}]}, {compile,[{options,[]}, {version,"4.8.1"}, {time,{2014,4,2,7,22,18}}, {source,"c:/Users/zhenxingluo/Documents/Erlang/helloworld.erl"}]}]
這里的cd是Erlang shell專用的一個函數,就是change directory。然后,我們調用Erlang shell中的編譯函數c()。如果編譯沒有報錯,我們就會看到helloworld.erl文件的旁邊,有生成一個文件叫helloworld.beam。
事實上,beam代表Bogdan/Bjorn's Erlang Abstract Machine;這個VM只是眾多Erlang的虛擬機之一,但是好像其他都不怎么用了。恩,繼續編譯沒有報錯的話,因為我們當前目錄是helloworld.beam所在目錄,所以,VM是看得見這個文件的。那么,我們就可以在shell窗口里直接調用他了。就像我們看到的那樣。
這里的module_info()是編譯器加入的一個函數,調用此函數可以查看我們module的metadata。同時,它也支持我們使用module_info/1,來查看其中的一項,比如,helloworld:module_info(attributes).