Erlang 从零 · 第 09 课(展开目录)
01 · 先打开 erl02 · 先认常见 term03 · 分清两种文字04 · 变量只接一次05 · 拆开一列,再筛一次06 · 把文字安全变成整数07 · 用 case 选一条路08 · 让函数继续走09 · 把代码放进模块10 · 条件必须说真话11 · 一层一层取数据12 · 文字先说清编码13 · 让函数子句排好队14 · 带着累加器往前走15 · 先看值,再选路16 · 给模块立规矩17 · 让 rebar3 管项目18 · 让结果有固定形状19 · 把磁盘当成会失败的邻居20 · 把函数交给 list21 · record 留在院里22 · 先写模块之间的约定23 · 把 term 形状写出来24 · 小测试与整段路25 · 把应用磨成命令26 · 先把小项目圈住27 · 把真实文件洗干净28 · 公开入口越少越稳29 · 把约定留在源码旁30 · 打包,也留下线索31 · 按最初任务交卷
ERLANG · 试跑 · LESSON 0940 分钟

把代码放进模块

把函数存进 `.erl` 文件,编译运行,并望见 BEAM 主线。

01 · 先看完整例子

保存为 village.erl

保存后,在同一目录打开 erl,运行 c(village).。

Erlang
%% 文件名与模块名保持一致
-module(village).

%% 允许模块外调用 greet/1
-export([greet/1]).

%% Name 是一个 binary
greet(Name) ->
  <<"hello, ", Name/binary>>.
先对照结果
  • c(village). 编译成功时返回 {ok,village}。
  • village:greet(<<"A-Ming">>). 返回 <<"hello, A-Ming">>。
02 · 回头拆代码

从第一行往下读

  1. -module(village). 声明模块名;文件名应是 village.erl。
  2. -export([greet/1]). 中的 /1 表示 greet 接收一个参数。
  3. Name/binary 把传入的 binary 接在固定文字后面。
  4. 编译后用 模块:函数(参数) 调用:village:greet(<<"A-Ming">>)。
03 · 认清新符号

符号不是暗号

-module(village).

声明模块名是 village,文件应叫 village.erl。

-export([greet/1]).

把接收 1 个参数的 greet 函数公开。

c(village).

在 Erlang shell 中编译并加载 village.erl。

04 · 代码里的概念

把名字和意思对上

01

模块

一个 .erl 文件中的函数集合。模块名通常与文件名相同。

02

export

列出允许模块外部调用的函数,格式是 name/arity。

03

进程

BEAM 中独立运行、通过消息合作的小执行单元。

05 · 再说清楚

这些写法为什么有用

shell 像草稿纸。需要保存和复用的命名函数通常放进模块文件,文件名与模块名保持一致。

-export([greet/1]). 允许模块外调用 greet/1。这里必须写 /1,因为同名但参数个数不同的函数是不同函数。

模块能运行后,就可以进入 BEAM 的强项:小进程、消息、监督和故障恢复。这些属于下一条主线。

设计选择

为什么这样写

为什么先用模块组织函数,而不是先造 class?

这次选择

模块给一组相关函数命名并形成公开边界。数据仍是显式参数,不藏在隐含的 this 里。

代价与边界

需要长期状态与生命周期时再引入进程;单纯把函数塞进 GenServer,只会多一层排队。

换一把尺子

从 Java、Python、JavaScript 看过来

Java

熟悉的起点
class 同时装数据、方法和对象身份。
BEAM 在意什么
BEAM 模块不是对象;函数没有隐含接收者。
别带错直觉
模块也不会因为被调用就启动一个进程。

Python

熟悉的起点
module 与 class 都能组织代码。
BEAM 在意什么
这里先让 module/function/arity 成为稳定入口,状态另外安排。
别带错直觉
OTP Application 也不是操作系统进程。

JavaScript

熟悉的起点
ES module 导出函数或 class。
BEAM 在意什么
编译后的模块由 BEAM 按模块名与 arity 调用。
别带错直觉
Mix/rebar3 管项目,不等于 BEAM 或 OTP。
06 · 自己改一次

合上答案,动手

增加一个公开函数 farewell/1,返回 <<"bye, ", Name/binary>>。

练习起点
-module(village).

%% 在列表中再公开一个函数
-export([greet/1, ____]).

greet(Name) ->
  <<"hello, ", Name/binary>>.

%% 补出新函数
____(Name) ->
  <<"bye, ", Name/binary>>.

目标结果: 重新编译后,village:farewell(<<"A-Ming">>). 返回 <<"bye, A-Ming">>。

卡住了,再看提示

导出列表的空填 farewell/1;函数定义的空只填 farewell。

运行过以后,再看一种答案
一种答案
-module(village).

%% 两个函数都接收一个参数
-export([greet/1, farewell/1]).

greet(Name) ->
  <<"hello, ", Name/binary>>.

%% 最后一条函数定义用句点结束
farewell(Name) ->
  <<"bye, ", Name/binary>>.
想一想:为什么导出列表写 `greet/1`,而不是只写 `greet`?

Erlang 用函数名和参数个数共同确认一个函数。greet/1 与 greet/2 是两个不同函数。

带走

记住三句

  1. 1

    模块名与 .erl 文件名保持一致。

  2. 2

    导出列表用 name/arity 准确写出函数。

  3. 3

    学完语法后,再进入 BEAM 的进程与消息。

本课结束代码也跑过一次,就把这一课收好。