Elixir 从零 · 第 07 课(展开目录)
01 · 先让代码说话02 · 先认六种值03 · 把值装起来04 · 从旧 list 生成新 list05 · 让形状对上06 · 用 case 做选择07 · 把文字安全变成整数08 · 拆开 &1 和管道09 · 把代码放进项目10 · 真假不只靠 true11 · 走进嵌套数据12 · 一字不一定一字节13 · 让函数按形状接活14 · 把一列数收成一个值15 · 给不同问题选不同路16 · 给 map 一张名片17 · 让项目自己验答案18 · 让成功和失败长得一样19 · 读写文件先管好路20 · 只算眼前需要的21 · 同一句话,不同做法22 · 先约好模块会做什么23 · 把数据形状写在门口24 · 测试不只看一条好路25 · 把项目磨成一个命令26 · 定下小项目边界27 · 把真实文字洗干净28 · 只开三扇门29 · 把约定钉在代码旁30 · 打包,也留下脚印31 · 照任务书交卷
ELIXIR · 试跑 · LESSON 0730 分钟

把文字安全变成整数

用 trim、匿名函数和 try/rescue 处理合法与错误输入。

01 · 先看完整例子

一个函数同时处理正确与错误输入

先运行合法输入,再运行错误输入;两次都由同一个匿名函数给出结果。

Elixir
# 匿名函数收好完整解析流程
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 接住,因此第二次调用也正常返回。
02 · 回头拆代码

从第一行往下读

  1. parse_integer = fn text -> ... end 创建完整匿名函数;调用时写 parse_integer.(文字)。
  2. 文档用 String.trim/1 说明函数名和参数个数;String.trim(text) 才是真正调用。它先去掉两端空白。
  3. String.to_integer(clean) 尝试把清理后的文字变成整数。合法时返回数字,外层把它装进 {:ok, number}。
  4. 错误文字会让 String.to_integer/1 抛出 ArgumentError;rescue 只接住这一类异常并返回 {:error, :not_integer}。
  5. 合法和错误分支都返回 tuple,后面的代码可以继续用模式匹配处理。
03 · 认清新符号

符号不是暗号

String.trim/1

String 模块、trim 函数、1 个参数。这是文档里的写法,不是一条调用。

String.trim(text)

把 text 作为参数,真正调用函数。

String.to_integer/1

把只含整数的字符串转成整数;格式不对时抛出 ArgumentError。

fn text -> ... end

创建一个接收 text 的完整匿名函数。

try ... rescue ... end

尝试转换;遇到指定异常时走错误分支。

04 · 代码里的概念

把名字和意思对上

01

arity

函数接收的参数个数。String.trim/1 和 String.to_integer/1 都接收 1 个参数。

02

匿名函数

用 fn ... -> ... end 创建的函数。这里把整个解析流程交给名字 parse_integer。

03

异常

函数无法继续正常工作时发出的错误信号。错误文字交给 String.to_integer/1 会产生 ArgumentError。

04

try/rescue

先尝试一段代码,再针对预期异常返回可处理的结果。这里只接 ArgumentError。

05 · 再说清楚

这些写法为什么有用

函数接收输入,做一件事,再返回输出。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 的另一种写法。
06 · 自己改一次

合上答案,动手

补完 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. 1

    文档用 模块.函数/参数个数 写清函数;真正调用时把参数写进括号。

  2. 2

    完整匿名函数可以把清理、转换和错误处理收成一个步骤。

  3. 3

    只接住预期异常,并把成功与失败都变成清楚的返回值。

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