![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pn5GcuQ0MlMWbidXND1ENZRkT1UEVPpHM51UeBRlT3VFVNlXSE1UeJRVT3lERNlHMD1UeBRkT6VEVNZXSU10dJRUT5hzQNlXQE5keFRVT2NmMiNnSywEd5ITW110MaZHetlVdO1GT3lERNl3YXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.png)
前幾篇文章會寫得比較基礎,但是既然要寫一系列的文章,還是得從基礎開始寫。我剛學Erlang碰到最大的問題是,想網上搜尋下文法,結果卻是寥寥無幾,而且介紹得不是很系統,對我了解一些細節是有影響的,正好我身邊有好多Erlang大神,遇到問題可以随時找他們請教,經過自己消化後,分享到這裡,希望可以幫助到一些人。這幾天偶爾逛一逛部落格園,發現這裡真是程式員的知識海洋,随便翻兩頁,就有很多大佬在編寫Java并發、Docker鏡像、K8S等技術文章,文章的品質我覺得都可以出書了。雖然我之前經常在CSDN,但是沒看過這麼專業的,看來程式大佬都在部落格園。
開始聊正題吧,今天聊到是子產品(Module),子產品就是存放代碼的地方。
前幾篇文章會寫得比較基礎,但是既然要寫一系列的文章,還是得從基礎開始寫。我剛學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)的使用,且聽下回分解。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pn5GcuQ0MlMWbidXND1ENZRkT1UEVPpHM51UeBRlT3VFVNlXSE1UeJRVT3lERNlHMD1UeBRkT6VEVNZXSU10dJRUT5hzQNlXQE5keFRVT2NmMiNnSywEd5ITW110MaZHetlVdO1GT3lERNl3YXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.png)