Erlang 从零 · 第 22 课(展开目录)
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 2240 分钟

先写模块之间的约定

用 behaviour callback 定义接口,再写可替换实现。

01 · 先看完整例子

分别保存为 greeter.erl 与 zh_greeter.erl

编译两个模块后运行 zh_greeter:hello(<<"小满"/utf8>>).。

Erlang
%% greeter.erl
-module(greeter).
-callback hello(binary()) -> binary().

%% zh_greeter.erl
-module(zh_greeter).
-behaviour(greeter).
-export([hello/1]).

%% 实现 behaviour 要求的 hello/1
hello(Name) ->
  <<"你好,"/utf8, Name/binary>>.
先对照结果
  • 得到包含 你好,小满 的 UTF-8 binary。
02 · 回头拆代码

从第一行往下读

  1. behaviour 模块只写约定,不提供这次的具体问候。
  2. 实现模块声明 behaviour 并导出 callback。
  3. 缺少 hello/1 时,编译器会给出警告。
03 · 认清新符号

符号不是暗号

-callback

声明回调函数和类型。

-behaviour(Module).

声明当前模块遵守该 behaviour。

Module:Function(Args)

通过传入模块调用具体实现。

04 · 代码里的概念

把名字和意思对上

01

behaviour

一组模块必须实现的函数约定。

02

callback

behaviour 声明、实现模块负责提供的函数。

05 · 再说清楚

这些写法为什么有用

behaviour 说明一个模块应提供哪些函数。实现模块可以不同,调用者只依赖共同约定。

-callback 声明 name/arity 及类型。实现模块写 -behaviour(Name).,编译器会提醒缺失 callback。

behaviour 常用在 OTP,也适合存储、通知等项目边界。

设计选择

为什么这样写

为什么 behaviour 只规定回调,不直接提供进程?

这次选择

behaviour 先约定模块必须实现哪些函数。GenServer/gen_server 是带运行时协议的 behaviour,但一般 behaviour 本身不会启动任何东西。

代价与边界

它统一边界,却不替你决定状态、监督、容量和错误策略。

换一把尺子

从 Java、Python、JavaScript 看过来

Java

熟悉的起点
interface 定义方法契约。
BEAM 在意什么
behaviour 约束 module/function/arity;调用者仍调用模块函数。
别带错直觉
实现 behaviour 不等于创建对象或线程。

Python

熟悉的起点
ABC 或 Protocol。
BEAM 在意什么
callback 元数据让工具检查实现,也能被 OTP 框架调用。
别带错直觉
它不是 Python 装饰器式的运行时包装。

JavaScript

熟悉的起点
约定对象形状或 TypeScript interface。
BEAM 在意什么
behaviour 存在于 BEAM 模块与 callback 入口层。
别带错直觉
类型约定和进程生命周期仍是两件事。
06 · 自己改一次

合上答案,动手

增加 quiet_greeter,忽略名字并返回 <<"hello">>。

练习起点
-module(quiet_greeter).
-behaviour(____).
-export([hello/1]).

hello(_Name) ->
  ____.

目标结果: 模块遵守 greeter,调用返回 <<"hello">>。

卡住了,再看提示

behaviour 名是 greeter,返回值是 binary。

运行过以后,再看一种答案
一种答案
-module(quiet_greeter).
-behaviour(greeter).
-export([hello/1]).

%% 这个实现选择忽略名字
hello(_Name) ->
  <<"hello">>.
想一想:behaviour 会自动生成 callback 正文吗?

不会。它提供约定和编译检查,具体函数仍由实现模块编写。

带走

记住三句

  1. 1

    behaviour 约束模块接口。

  2. 2

    callback 写清 name/arity 与类型。

  3. 3

    依赖约定可替换具体实现。

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