Elixir 从零 · 第 07 课(展开目录)
把文字安全变成整数
用 trim、匿名函数和 try/rescue 处理合法与错误输入。
一个函数同时处理正确与错误输入
先运行合法输入,再运行错误输入;两次都由同一个匿名函数给出结果。
# 匿名函数收好完整解析流程
parse_integer = fn text ->
clean = String.trim(text)
try do
{:ok, String.to_integer(clean)}
rescue
ArgumentError -> {:error, :not_integer}
end
end
# 合法文字会得到整数
good = parse_integer.(" 42 ")
# 错误文字会得到可处理的错误 tuple
bad = parse_integer.("松")
{good, bad}good是{:ok, 42}。bad是{:error, :not_integer}。ArgumentError被rescue接住,因此第二次调用也正常返回。
从第一行往下读
parse_integer = fn text -> ... end创建完整匿名函数;调用时写parse_integer.(文字)。- 文档用
String.trim/1说明函数名和参数个数;String.trim(text)才是真正调用。它先去掉两端空白。 String.to_integer(clean)尝试把清理后的文字变成整数。合法时返回数字,外层把它装进{:ok, number}。- 错误文字会让
String.to_integer/1抛出ArgumentError;rescue只接住这一类异常并返回{:error, :not_integer}。 - 合法和错误分支都返回 tuple,后面的代码可以继续用模式匹配处理。
符号不是暗号
String.trim/1String 模块、trim 函数、1 个参数。这是文档里的写法,不是一条调用。
String.trim(text)把 text 作为参数,真正调用函数。
String.to_integer/1把只含整数的字符串转成整数;格式不对时抛出 ArgumentError。
fn text -> ... end创建一个接收 text 的完整匿名函数。
try ... rescue ... end尝试转换;遇到指定异常时走错误分支。
把名字和意思对上
arity
函数接收的参数个数。String.trim/1 和 String.to_integer/1 都接收 1 个参数。
匿名函数
用 fn ... -> ... end 创建的函数。这里把整个解析流程交给名字 parse_integer。
异常
函数无法继续正常工作时发出的错误信号。错误文字交给 String.to_integer/1 会产生 ArgumentError。
try/rescue
先尝试一段代码,再针对预期异常返回可处理的结果。这里只接 ArgumentError。
这些写法为什么有用
函数接收输入,做一件事,再返回输出。String.trim(" 42 ") 先去掉文字两端空白,得到 "42"。
文档常写 String.trim/1。斜杠后的 1 叫 arity,中文常说“参数个数”。它表示这个函数接收 1 个参数。
String.to_integer/1 把数字文字变成整数;遇到 "松" 这类输入时会抛出 ArgumentError。
完整匿名函数用 fn ... -> ... end 包住流程。try/rescue 接住预期中的 ArgumentError,让函数返回清楚的错误 tuple,而不是让 IEx 中断这次调用。
为什么这样写
为什么函数名后还要写 /1?
BEAM 用“模块、函数名、参数个数”共同确定入口。trim/1 就是接收一个参数的 trim。
同名不同参数个数是不同函数;默认参数只是编译时生成多个 arity,不能把它想成运行时随意少传。
从 Java、Python、JavaScript 看过来
Java
- 熟悉的起点
- 方法可按参数类型和数量重载。
- BEAM 在意什么
- BEAM 先按名称与 arity 找函数,再由子句按数据形状匹配。
- 别带错直觉
run/1与run/2是两个明确入口。
Python
- 熟悉的起点
- 一个函数常靠默认参数、
*args接收不同数量。 - BEAM 在意什么
- arity 是函数身份的一部分,并写进导出列表和文档。
- 别带错直觉
- 少传一个参数不会自动得到另一个 arity。
JavaScript
- 熟悉的起点
- 函数通常容忍少传或多传参数。
- BEAM 在意什么
- BEAM 调用必须找到精确 arity。
- 别带错直觉
&1是匿名函数参数占位,不是/1的另一种写法。
合上答案,动手
补完 parse_age:" 12 " 返回 {:ok, 12},"未知" 返回 {:error, :not_integer}。
parse_age = fn text ->
clean = String.____(text)
try do
{:ok, String.____(clean)}
rescue
____ -> {:error, :not_integer}
end
end
{parse_age.(" 12 "), parse_age.("未知")}目标结果: 得到 {{:ok, 12}, {:error, :not_integer}}。
卡住了,再看提示
三个空依次是 trim、to_integer 和 ArgumentError。
运行过以后,再看一种答案
# 先清理,再尝试转换
parse_age = fn text ->
clean = String.trim(text)
try do
{:ok, String.to_integer(clean)}
rescue
ArgumentError -> {:error, :not_integer}
end
end
{parse_age.(" 12 "), parse_age.("未知")}想一想:为什么 `parse_integer.("松")` 没有让整段代码停下来?
String.to_integer/1 抛出的 ArgumentError 被 rescue 接住,并转换成了 {:error, :not_integer}。
记住三句
- 1
文档用
模块.函数/参数个数写清函数;真正调用时把参数写进括号。 - 2
完整匿名函数可以把清理、转换和错误处理收成一个步骤。
- 3
只接住预期异常,并把成功与失败都变成清楚的返回值。