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

让结果有固定形状

用 `{ok, Value}` 与 `{error, Reason}` 统一成功和失败。

01 · 先看完整例子

保存为 ratio.erl

编译后分别运行 ratio:divide(12,3). 与 ratio:divide(12,0).。

Erlang
-module(ratio).
-export([divide/2]).

%% 最具体的零除数规则放在前面
divide(_Left, 0) ->
  {error, division_by_zero};
divide(Left, Right) ->
  {ok, Left / Right}.
先对照结果
  • 结果依次为 {ok,4.0} 与 {error,division_by_zero}。
02 · 回头拆代码

从第一行往下读

  1. 两个子句的返回值都是二元 tuple。
  2. 第一项稳定说明成功或失败。
  3. 调用者可以只处理自己关心的 reason,也可保留兜底分支。
03 · 认清新符号

符号不是暗号

{ok, Value}

成功标签与结果组成 tuple。

{error, Reason}

失败标签与原因组成 tuple。

case

在调用边界匹配两种结果。

04 · 代码里的概念

把名字和意思对上

01

结果约定

函数与调用者共同遵守的返回值形状。

02

错误原因

可由模式匹配识别的失败类别。

05 · 再说清楚

这些写法为什么有用

可能失败的函数应让调用者一眼看见两种结果。固定 tuple 比特殊数字或模糊 atom 更可靠。

成功常用 {ok, Value},失败常用 {error, Reason}。这也是 OTP 库里常见的约定。

Reason 使用稳定 atom 或 tuple。面向人的提示留到 shell、HTTP 或 CLI 边界再翻译。

设计选择

为什么这样写

为什么常返回 {:ok, value} 或 {ok, Value}?

这次选择

预料之中的失败也写进返回值。调用者能用模式列出成功、失败和兜底分支。

代价与边界

真正意外、无法在本层处理的错误仍可以抛出,让监督边界接手。把所有异常都吞成 :error 会丢掉证据。

换一把尺子

从 Java、Python、JavaScript 看过来

Java

熟悉的起点
checked/unchecked exception 或 Optional。
BEAM 在意什么
tagged result 把常见业务结果放在普通控制流中。
别带错直觉
不是每个错误都该 crash,也不是每个异常都该 catch。

Python

熟悉的起点
异常与 None。
BEAM 在意什么
tag 明说失败类别,避免只靠一个空值猜原因。
别带错直觉
边界处可以把第三方异常翻译成稳定 result。

JavaScript

熟悉的起点
throw、rejected Promise 或 null。
BEAM 在意什么
tuple 不是 Promise;它是已经拿到手的数据契约。
别带错直觉
不要把课程里的 contract 误读成异步 Promise。
06 · 自己改一次

合上答案,动手

补全 positive/1,只接受正数。

练习起点
positive(Number) when Number > 0 ->
  {____, Number};
positive(_) ->
  {____, ____}.

目标结果: 正数得到 {ok,Number},其他输入得到 {error,not_positive}。

卡住了,再看提示

成功和失败标签分别是 ok、error。

运行过以后,再看一种答案
一种答案
positive(Number) when Number > 0 ->
  {ok, Number};
%% 原因使用稳定 atom
positive(_) ->
  {error, not_positive}.
想一想:为什么失败时不只返回 `error`?

单个 atom 没有原因位置;统一二元 tuple 后,调用者能匹配类别并继续携带细节。

带走

记住三句

  1. 1

    成功与失败要有稳定外形。

  2. 2

    Reason 应短而可匹配。

  3. 3

    错误显示留给系统边界。

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