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

公开入口越少越稳

让统计模块只接收行 list,把分词 helper 留在内部。

01 · 先看完整例子

保存为 trail_stats.erl

本项目约定字符数不包含换行,词由 Unicode 空白分隔。

Erlang
-module(trail_stats).
-export([count/1]).

%% 对外只开放 count/1
count(Lines) ->
  #{
    lines => length(Lines),
    words => lists:sum([word_count(Line) || Line <- Lines]),
    chars => char_count(Lines)
  }.

word_count(Line) ->
  length(string:lexemes(Line, " 	")).

char_count(Lines) ->
  Text = iolist_to_binary(Lines),
  length(unicode:characters_to_list(Text)).
先对照结果
  • trail_stats:count([<<"hello beam">>, <<"again">>]). 返回行 2、词 3、字符 15。
02 · 回头拆代码

从第一行往下读

  1. 公开函数返回固定三键 map。
  2. 两个 helper 未导出,可在模块内部修改。
  3. 先合并 binary 再按 Unicode 字符计数。
03 · 认清新符号

符号不是暗号

-export([count/1]).

只公开一个统计入口。

maps:get/2

CLI 可按键读取统计结果。

string:lexemes/2

按给定分隔字符把 Unicode 文字拆成词。

04 · 代码里的概念

把名字和意思对上

01

公开 API

模块外允许依赖的 name/arity 与返回 term。

02

私有 helper

未导出、只帮助当前模块完成工作的函数。

05 · 再说清楚

这些写法为什么有用

模块公开函数越多,调用者越容易依赖内部细节。本项目只公开真正需要的入口。

统计模块接收 Parser 已经清洗的行,不再读文件。行、词、字符规则集中在这里。

内部 helper 不放进 -export。返回 map 给字段命名,也便于 CLI 读取。

06 · 自己改一次

合上答案,动手

写出空输入的期待 map。

练习起点
?assertEqual(
  #{lines => ____, words => ____, chars => ____},
  trail_stats:count([])
).

目标结果: 三个计数都为 0。

卡住了,再看提示

空 list 没有行、词和字符。

运行过以后,再看一种答案
一种答案
%% 空输入是公开 API 的重要边界
?assertEqual(
  #{lines => 0, words => 0, chars => 0},
  trail_stats:count([])
).
想一想:为什么 `word_count/1` 不放进 export 列表?

它只是 count/1 的实现细节,调用者不需要依赖分词过程。

带走

记住三句

  1. 1

    公开 API 要小而明确。

  2. 2

    模块之间传稳定 term。

  3. 3

    内部 helper 不轻易导出。

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