前几篇文章会写得比较基础,但是既然要写一系列的文章,还是得从基础开始写。我刚学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,代码如下:

  1. -module(hello_world).

  2. -export([hello/0]).
  3.  
  4. hello() ->
  5. "Hello Erlang".
  6.  
  7. world() ->
  8. "Hello World".

  这个模块非常简单,只有2个函数,分别是hello和world。这里有几个概念,module(模块)、export(函数导出列表)、函数。

  export里面只有hello,说明其它模块只能访问到hello函数,无法访问到world函数。hello类似于Java声明为public公有函数,world类似于private私有函数。

  现在来编译下hello_world模块,并分别执行下2个函数看下返回信息:

  1. Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
  2.  
  3. Eshell V11.1.3 (abort with ^G)
  4. 1> ls(). %% ls()函数在终端显示当前目录下的所有文件,输入help().可查看所有命令
  5. hello_world.erl
  6. ok
  7. 2> c(hello_world). %% c()函数在终端编译hello_world模块,注意不能加.erl后缀
  8. hello_world.erl:18: Warning: function world/0 is unused %% 这里是个警告,提醒world函数没有导出
  9. {ok,hello_world}
  10. 3> m(hello_world). %% m()函数在终端显示hello_world模块信息,可以查看该模块的基本信息和导出函数列表
  11. Module: hello_world
  12. MD5: f7866776c11b9cfc904dc569bafe7995
  13. Compiled: No compile time info available
  14. Object file: /Users/snowcicada/code/erlang-story/story002/hello_world.beam
  15. Compiler options: []
  16. Exports:
  17. hello/0
  18. module_info/0
  19. module_info/1
  20. ok
  21. 4> hello_world:hello(). %% M:F()是Erlang的基本调用方式,M表示模块名,F表示函数名
  22. "Hello Erlang" %% 这里就是hello函数的返回结果
  23. 5> hello_world:world(). %% 由于world函数没有导出,没有加入export导出列表,所以调用没导出的函数,会得到一个错误
  24. ** exception error: undefined function hello_world:world/0

知识点2:编写一个有头文件的Hello World模块

  创建一个文件hello_world.hrl,就一行代码,内容如下:

  1. -define(TEXT, "Hello World").

  使用define声明了一个宏TEXT,这里的宏跟C语言的宏类似,语法差不多。

  修改hello_world.erl,引用下头文件,代码如下:

  1. -module(hello_world).-include("hello_world.hrl").
  2.  
  3. %% API
  4. -export([hello/0, world/0]).
  5.  
  6. hello() ->
  7. "Hello Erlang".
  8.  
  9. world() ->
  10. ?TEXT. %% 注意这行

  Erlang要使用宏,需要在宏的前面加一个问号?,不加编译不过。

  重新编译下hello_world模块,执行结果如下:

  1. Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
  2.  
  3. Eshell V11.1.3 (abort with ^G)
  4. 1> ls().
  5. hello_world.beam hello_world.erl hello_world.hrl
  6.  
  7. ok
  8. 2> c(hello_world).
  9. {ok,hello_world}
  10. 3> m(hello_world).
  11. Module: hello_world
  12. MD5: ceb4d19017c728b4f338ba92ea7bc0cb
  13. Compiled: No compile time info available
  14. Object file: /Users/guozs/code/erlang-story/story002/hello_world.beam
  15. Compiler options: []
  16. Exports:
  17. hello/0
  18. module_info/0
  19. module_info/1
  20. world/0
  21. ok
  22. 4> hello_world:world().
  23. "Hello World"

知识点3:模块之间可以相互调用,但是不能有循环调用

  Erlang的模块可以相互调用,比如在其他语言经常会出现A包含B,B包含A的问题,但是在Erlang这里,只要避免2个模块的函数不互相循环调用,就不会有问题。什么意思呢?假设A模块有一个函数a,B模块有一个函数b,A:a调用了B:b,B:b调用了A:a,那么这样就已经循环调用了,这是不允许出现的。

  创建一个文件a.erl,代码如下:

  1. -module(a).
  2.  
  3. %% API
  4. -export([a/0]).
  5.  
  6. a() ->
  7. b:b().

  创建一个文件b.erl,代码如下:

  1. -module(b).%% API
  2. -export([b/0]).
  3.  
  4. b() ->
  5. a:a().

  执行结果:

  1. Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
  2.  
  3. Eshell V11.1.3 (abort with ^G)
  4. 1> c(a).
  5. {ok,a}
  6. 2> c(b).
  7. {ok,b}
  8. 3> a:a(). %% 这里卡死了,只能执行Ctrl+C强制退出
  9.  
  10. BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
  11. (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution

  程序卡死了,只能强制退出,所以模块虽然可以互相引用对方的函数,但是要注意避免循环调用问题。

知识点4:引入模块函数

  创建一个文件calc.erl,代码如下:

  1. -module(calc).
  2.  
  3. %% API
  4. -export([add/2]).
  5.  
  6. add(A, B) ->
  7. A + B.

  修改hello_world.erl,引入calc模块的函数,代码如下:

  1. -module(hello_world).
  2.  
  3. -include("hello_world.hrl").
  4.  
  5. %% API
  6. -export([hello/0, world/0, mod_add/2]).
  7.  
  8. -import(calc, [add/2]). %% 这里引入calc模块
  9.  
  10. hello() ->
  11. "Hello Erlang".
  12.  
  13. world() ->
  14. ?TEXT.
  15.  
  16. mod_add(A, B) ->
  17. add(A, B).

  一行import只能引入一个模块,至于要引入多少函数,可以灵活选择。

  执行结果:

  1. Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
  2.  
  3. Eshell V11.1.3 (abort with ^G)
  4. 1> c(calc).
  5. {ok,calc}
  6. 2> c(hello_world).
  7. {ok,hello_world}
  8. 3> hello_world:mod %% Tab键可以智能提示
  9. mod_add/2 module_info/0 module_info/1
  10. 3> hello_world:mod_add(1, 2).
  11. 3

知识点5:导出所有函数(export_all)

  首先声明,export_all要避免使用,因为会将所有的函数对外导出,会存在一些设计理念的问题。不使用export_all的好处有几个,

  1、安全性:比如当您重构模块时,您可以知道哪些功能可以安全地重命名,而不需要到外部查找依赖,万一修改了,导致其他模块调用失败也是有可能的;

  2、代码气味:编译时不会收到警告;

  3、清晰度:更容易看出在模块之外使用哪些功能。

  在函数顶部加入一行:-compile(export_all).,即可导出所有函数,但是编译时会收到一个警告。

  修改calc.erl,代码如下:

  1. -module(calc).
  2.  
  3. %% API
  4. %%-export([add/2]).
  5. -compile(export_all).
  6.  
  7. add(A, B) ->
  8. A + B.

  执行结果:

  1. Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
  2.  
  3. Eshell V11.1.3 (abort with ^G)
  4. 1> c(calc).
  5. calc.erl:14: Warning: export_all flag enabled - all functions will be exported %% 这里会有警告
  6. {ok,calc}
  7. 2> c(hello_world).
  8. {ok,hello_world}
  9. 3> hello_world:mod_add(1,2).
  10. 3

  模块的内容就先讲到这了,这一回只介绍模块本身,以后会经常编写代码,使用模块就是家常便饭了。

  本文使用的代码已上传Github:https://github.com/snowcicada/erlang-story/tree/main/story002

  下一回将介绍函数(Function)的使用,且听下回分解。

  

  作者:snowcicada
  本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

Erlang那些事儿第2回之我是模块(module),一文件一模块的更多相关文章

  1. Erlang那些事儿第3回之我是函数(fun),万物之源MFA

    Erlang代码到处都是模式匹配,这把屠龙刀可是Erlang的看家本领.独家绝学,之前在<Erlang那些事儿第1回之我是变量,一次赋值永不改变>文章提到过,Erlang一切皆是模式匹配. ...

  2. Erlang那些事儿第1回之我是变量,一次赋值永不改变

    第1回先从不变的变量说开来,学过其他编程语言的人都知道,变量之所以叫变量,是因为它会经常变,被修改.假设原本X  = 10,后来再执行X = 24,那么X就从10变成了24,这对于程序新手和老鸟来说, ...

  3. centos 安装MATLAB :设置回环设备失败: 没有那个文件或目录

    基本参数:centos 7 x86_64,linux 系统, 安装matlab, 已经下载R2016b_glnxa64.iso 但挂载的时候遇到问题: [root@lf mnt]# mount -o  ...

  4. Erlang那些事儿之正儿八经的前言

    说在前面,为啥要码这些,并不是因为喜欢它,恰恰相反,我非常讨厌Erlang(真香警告)这位二郎神(Erlang的谐音),讨厌它的语法,讨厌它不变的变量,讨厌它的一切. 曾经的我,一听到这个语言,我就打 ...

  5. 缓存篇(Cache)~第三回 HttpModule实现网页的文件级缓存

    返回目录 再写完缓存篇第一回之后,得到了很多朋友的好评和来信,所以,决定加快步伐,尽快把剩下的文章写完,本篇是第三回,主要介绍使用HttpModule实现的文件级缓存,在看本文之前,大家需要限度Htt ...

  6. 从码云把之前的代码git push 回IDEA 对IDEA里的文件进行简单操作

    前情提要:我的IDEA里的项目之前已经和码云连接成功可以上传.但我直接在电脑文件夹里对文件进行重命名.剪切.粘贴等操作之后IDEA对操作后的文件不识别,无奈之下我将码云上之前的代码推回重新新建了项目. ...

  7. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】【实验一】流水灯模块

    实验一:流水灯模块 对于发展商而言,动土仪式无疑是最重要的任务.为此,流水灯实验作为低级建模II的动土仪式再适合不过了.废话少说,我们还是开始实验吧. 图1.1 实验一建模图. 如图1.1 所示,实验 ...

  8. 我是一个云Hosts文件,用来干啥你懂的

    Smarthosts是一个托管在谷歌代码上的项目,您可以轻松利用本项目使用到一份稳定的Hosts文件. 这份Hosts文件可以帮助您顺利打开一些网站,提高某些国外服务的打开或下载速度. http:// ...

  9. .net core 中的 DependencyInjection - IOC

    概要:因为不知道写啥,所以随便找个东西乱说几句,嗯,就这样,就是这个目的. 1.IOC是啥呢? IOC - Inversion of Control,即控制反转的意思,这里要搞明白的就是,它是一种思想 ...

随机推荐

  1. 【PUPPETEER】初探之原生frame切换(四)

    一.知识点 page.frames() 使用frame.url() 获取framed的url x.getAttribute('x') 获取元素内值 二.实例 问:什么是iframe? 答:iframe ...

  2. C语言讲义——内联函数

    如果一些函数被频繁调用,不断地有函数入栈(Stack),会造成栈空间的大量消耗. 对应这种问题,可以使用内联函数(inline). 编译器会将内联函数的代码整段插入到调用的位置. #include & ...

  3. 【服务总线 Azure Service Bus】Service Bus在使用预提取(prefetching)后出现Microsoft.Azure.ServiceBus.MessageLockLostException异常问题

    问题描述 Service Bus接收端的日志中出现大量的MessageLockLostException异常.完整的错误消息为: Microsoft.Azure.ServiceBus.MessageL ...

  4. java44

    1.使用封装工具类思想:三种输入模式下的工具类. dateUtils类, StringUtils类(判断字符串值是否为空), 调用工具类: String res = dateUtils.datetoS ...

  5. vue+element ctrl+s保存写法

    <el-input type="textarea" ref="inppp" v-model="values" placeholder= ...

  6. 第二十四章、containers容器类部件QScrollArea滚动区域详解

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 容器部件就是可以在部件内放置其他部件的部件,在Qt Designer中可以使用的容器部件有 ...

  7. PyQt(Python+Qt)学习随笔:QTableWidget表格部件中行高和列宽的计算方式

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QTableWidget表格部件中行高和列宽的计算在Qt提供的资料中内容介绍比较泛,细节说得不清楚, ...

  8. 从Linux源码看Socket(TCP)的accept

    从Linux源码看Socket(TCP)的accept 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就从Linux源码的角度看下Serve ...

  9. JAVA环境安装及其配置

    一.JAVA版本的选择 我使用的是JAVA8,所以这次方法是JAVA8的安装过程. 这里我给出其下载地址,可以自行下载. 链接: https://pan.baidu.com/s/1k2Xydi6FJ2 ...

  10. Web前端-按钮点击效果(水波纹)

    这种效果可以由元素内嵌套canves实现,也可以由css3实现. Canves实现 网上摘了一份canves实现的代码,略微去掉了些重复定义的样式并且给出js注释,代码如下 第一种方法: html骨架 ...