前幾篇文章會寫得比較基礎,但是既然要寫一系列的文章,還是得從基礎開始寫。我剛學Erlang碰到最大的問題是,想網上搜尋下語法,結果卻是寥寥無幾,而且介紹得不是很系統,對我瞭解一些細節是有影響的,正好我身邊有好多Erlang大神,遇到問題可以隨時找他們請教,經過自己消化後,分享到這裡,希望可以幫助到一些人。這幾天偶爾逛一逛部落格園,發現這裡真是程式設計師的知識海洋,隨便翻兩頁,就有很多大佬在編寫Java併發、Docker映象、K8S等技術文章,文章的質量我覺得都可以出書了。雖然我之前經常在CSDN,但是沒看過這麼專業的,看來程式大佬都在部落格園。
開始聊正題吧,今天聊到是模組(Module),模組就是存放程式碼的地方。
C語言有.h標頭檔案和.c原始檔,同理,Erlang程式碼也有這2個玩意兒,只不過字尾有點區別,Erlang的標頭檔案字尾為.hrl,原始檔的字尾為.erl。每個Erlang原始檔都是一個模組,模組名就是檔名稱,每個.erl模組編譯後會產生一個.beam檔案,就好比.java類編譯後會產生一個.class檔案。
知識點1:編寫一個Hello World模組
建立一個檔案hello_world.erl,程式碼如下:
-module(hello_world).
-export([hello/0]). hello() -> "Hello Erlang". world() -> "Hello World".
這個模組非常簡單,只有2個函式,分別是hello和world。這裡有幾個概念,module(模組)、export(函式匯出列表)、函式。
export裡面只有hello,說明其它模組只能訪問到hello函式,無法訪問到world函式。hello類似於Java宣告為public公有函式,world類似於private私有函式。
現在來編譯下hello_world模組,並分別執行下2個函式看下返回資訊:
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace] Eshell V11.1.3 (abort with ^G) 1> ls(). %% ls()函式在終端顯示當前目錄下的所有檔案,輸入help().可檢視所有命令 hello_world.erl ok 2> c(hello_world). %% c()函式在終端編譯hello_world模組,注意不能加.erl字尾 hello_world.erl:18: Warning: function world/0 is unused %% 這裡是個警告,提醒world函式沒有匯出 {ok,hello_world} 3> m(hello_world). %% m()函式在終端顯示hello_world模組資訊,可以檢視該模組的基本資訊和匯出函式列表 Module: hello_world MD5: f7866776c11b9cfc904dc569bafe7995 Compiled: No compile time info available Object file: /Users/snowcicada/code/erlang-story/story002/hello_world.beam Compiler options: [] Exports: hello/0 module_info/0 module_info/1 ok 4> hello_world:hello(). %% M:F()是Erlang的基本呼叫方式,M表示模組名,F表示函式名 "Hello Erlang" %% 這裡就是hello函式的返回結果 5> hello_world:world(). %% 由於world函式沒有匯出,沒有加入export匯出列表,所以呼叫沒匯出的函式,會得到一個錯誤 ** exception error: undefined function hello_world:world/0
知識點2:編寫一個有標頭檔案的Hello World模組
建立一個檔案hello_world.hrl,就一行程式碼,內容如下:
-define(TEXT, "Hello World").
使用define宣告瞭一個巨集TEXT,這裡的巨集跟C語言的巨集類似,語法差不多。
修改hello_world.erl,引用下標頭檔案,程式碼如下:
-module(hello_world).-include("hello_world.hrl"). %% API -export([hello/0, world/0]). hello() -> "Hello Erlang". world() -> ?TEXT. %% 注意這行
Erlang要使用巨集,需要在巨集的前面加一個問號?,不加編譯不過。
重新編譯下hello_world模組,執行結果如下:
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace] Eshell V11.1.3 (abort with ^G) 1> ls(). hello_world.beam hello_world.erl hello_world.hrl ok 2> c(hello_world). {ok,hello_world} 3> m(hello_world). Module: hello_world MD5: ceb4d19017c728b4f338ba92ea7bc0cb Compiled: No compile time info available Object file: /Users/guozs/code/erlang-story/story002/hello_world.beam Compiler options: [] Exports: hello/0 module_info/0 module_info/1 world/0 ok 4> hello_world:world(). "Hello World"
知識點3:模組之間可以相互呼叫,但是不能有迴圈呼叫
Erlang的模組可以相互呼叫,比如在其他語言經常會出現A包含B,B包含A的問題,但是在Erlang這裡,只要避免2個模組的函式不互相迴圈呼叫,就不會有問題。什麼意思呢?假設A模組有一個函式a,B模組有一個函式b,A:a呼叫了B:b,B:b呼叫了A:a,那麼這樣就已經迴圈呼叫了,這是不允許出現的。
建立一個檔案a.erl,程式碼如下:
-module(a). %% API -export([a/0]). a() -> b:b().
建立一個檔案b.erl,程式碼如下:
-module(b).%% API -export([b/0]). b() -> a:a().
執行結果:
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace] Eshell V11.1.3 (abort with ^G) 1> c(a). {ok,a} 2> c(b). {ok,b} 3> a:a(). %% 這裡卡死了,只能執行Ctrl+C強制退出 BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
程式卡死了,只能強制退出,所以模組雖然可以互相引用對方的函式,但是要注意避免迴圈呼叫問題。
知識點4:引入模組函式
建立一個檔案calc.erl,程式碼如下:
-module(calc). %% API -export([add/2]). add(A, B) -> A + B.
修改hello_world.erl,引入calc模組的函式,程式碼如下:
-module(hello_world). -include("hello_world.hrl"). %% API -export([hello/0, world/0, mod_add/2]). -import(calc, [add/2]). %% 這裡引入calc模組 hello() -> "Hello Erlang". world() -> ?TEXT. mod_add(A, B) -> add(A, B).
一行import只能引入一個模組,至於要引入多少函式,可以靈活選擇。
執行結果:
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace] Eshell V11.1.3 (abort with ^G) 1> c(calc). {ok,calc} 2> c(hello_world). {ok,hello_world} 3> hello_world:mod %% 按Tab鍵可以智慧提示 mod_add/2 module_info/0 module_info/1 3> hello_world:mod_add(1, 2). 3
知識點5:匯出所有函式(export_all)
首先宣告,export_all要避免使用,因為會將所有的函式對外匯出,會存在一些設計理念的問題。不使用export_all的好處有幾個,
1、安全性:比如當您重構模組時,您可以知道哪些功能可以安全地重新命名,而不需要到外部查詢依賴,萬一修改了,導致其他模組呼叫失敗也是有可能的;
2、程式碼氣味:編譯時不會收到警告;
3、清晰度:更容易看出在模組之外使用哪些功能。
在函式頂部加入一行:-compile(export_all).,即可匯出所有函式,但是編譯時會收到一個警告。
修改calc.erl,程式碼如下:
-module(calc). %% API %%-export([add/2]). -compile(export_all). add(A, B) -> A + B.
執行結果:
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace] Eshell V11.1.3 (abort with ^G) 1> c(calc). calc.erl:14: Warning: export_all flag enabled - all functions will be exported %% 這裡會有警告 {ok,calc} 2> c(hello_world). {ok,hello_world} 3> hello_world:mod_add(1,2). 3
模組的內容就先講到這了,這一回只介紹模組本身,以後會經常編寫程式碼,使用模組就是家常便飯了。
本文使用的程式碼已上傳Github:https://github.com/snowcicada/erlang-story/tree/main/story002
下一回將介紹函式(Function)的使用,且聽下回分解。