Erlang 从零 · 第 06 课(展开目录)
把文字安全变成整数
用 trim、匿名函数和 try/catch 处理合法与错误输入。
一个函数同时处理正确与错误输入
先运行合法输入,再运行错误输入;两次都由同一个匿名函数给出结果。
%% 清掉旧绑定,再定义完整解析函数
f().
ParseInteger =
fun(Text) ->
Clean = string:trim(Text),
try binary_to_integer(Clean) of
Number -> {ok, Number}
catch
error:badarg -> {error, not_integer}
end
end.
%% 合法 binary 会得到整数
Good = ParseInteger(<<" 42 ">>).
%% 错误 binary 会得到可处理的错误 tuple
Bad = ParseInteger(<<"river">>).
{Good, Bad}.Good是{ok, 42}。Bad是{error, not_integer}。error:badarg被catch接住,因此第二次调用也正常返回。
从第一行往下读
ParseInteger = fun(Text) -> ... end创建完整匿名函数;调用时直接写ParseInteger(Binary)。- 文档用
string:trim/1说明函数名和参数个数;string:trim(Text)才是真正调用。它先去掉两端空白。 binary_to_integer(Clean)尝试转换;正常时of中的Number接住整数,并返回{ok, Number}。- 错误 binary 会让转换函数抛出
error:badarg;catch精确匹配它,再返回{error, not_integer}。 - 成功和失败都返回 tuple,后面的代码可以继续模式匹配。
符号不是暗号
string:trim/1string 模块、trim 函数、1 个参数。这是文档里的写法,不能直接运行。
string:trim(Text)把变量 Text 交给函数。这是在调用。
binary_to_integer/1把只含整数的 binary 转成整数;格式不对时抛出 error:badarg。
fun(Text) -> ... end创建一个接收 Text 的完整匿名函数。
try ... of ... catch ... end尝试转换;正常结果走 of,指定异常走 catch。
把名字和意思对上
arity
函数接收的参数个数。string:trim/1 和 binary_to_integer/1 都接收 1 个参数。
匿名函数
用 fun ... end 创建的函数。这里把完整解析流程交给变量 ParseInteger。
error:badarg
参数形状不合要求时发出的异常。非数字 binary 交给 binary_to_integer/1 会触发它。
try/catch
先尝试一段代码,再按异常类别和原因返回可处理的结果。
这些写法为什么有用
函数接收输入,返回输出。string:trim(<<" 42 ">>) 先去掉 binary 两端空白,得到 <<"42">>。
文档里的 string:trim/1 表示:string 模块、trim 函数、1 个参数。/1 叫 arity,不是除法,也不是一条能直接粘进 shell 的调用。
binary_to_integer/1 把数字 binary 变成整数;遇到 <<"river">> 这类输入时会抛出 error:badarg。
完整匿名函数用 fun ... end 包住流程。try ... of ... catch ... end 接住预期中的 error:badarg,返回清楚的错误 tuple。
为什么这样写
为什么函数名后还要写 /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的另一种写法。
合上答案,动手
补完 ParseAge:<<" 12 ">> 返回 {ok, 12},<<"unknown">> 返回 {error, not_integer}。
f().
ParseAge =
fun(Text) ->
Clean = string:____(Text),
try ____(Clean) of
Number -> {ok, Number}
catch
error:____ -> {error, not_integer}
end
end.
{ParseAge(<<" 12 ">>), ParseAge(<<"unknown">>)}.目标结果: 得到 {{ok, 12}, {error, not_integer}}。
卡住了,再看提示
三个空依次是 trim、binary_to_integer 和 badarg。
运行过以后,再看一种答案
%% 清理文字,再尝试转换
f().
ParseAge =
fun(Text) ->
Clean = string:trim(Text),
try binary_to_integer(Clean) of
Number -> {ok, Number}
catch
error:badarg -> {error, not_integer}
end
end.
{ParseAge(<<" 12 ">>), ParseAge(<<"unknown">>)}.想一想:为什么 `ParseInteger(<<"river">>)` 没有让整段代码停下来?
binary_to_integer/1 抛出的 error:badarg 被 catch 接住,并转换成了 {error, not_integer}。
记住三句
- 1
文档用
模块:函数/参数个数写清函数;真正调用时把参数写进括号。 - 2
完整匿名函数可以把清理、转换和错误处理收成一个步骤。
- 3
try/catch只接住预期异常,并把失败变成清楚的返回值。