Erlang那些事兒第2回之我是模塊(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)的使用,且聽下回分解。

  

 

  作者: snowcicada
  本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM