Elixir 流程控制详解

本章,我们将学习casecondif控制流结构。

case允许我们将一个值与多个模式进行匹配,直到匹配成功:

iex> case {1, 2, 3} do
...>   {4, 5, 6} ->
...>     "This clause won't match"
...>   {1, x, 3} ->
...>     "This clause will match and bind x to 2 in this clause"
...>   _ ->
...>     "This clause would match any value"
...> end
"This clause will match and bind x to 2 in this clause"

如果你想对已存在的变量进行模式匹配,你需要使用^操作符:

iex> x = 1
1
iex> case 10 do
...>   ^x -> "Won't match"
...>   _  -> "Will match"
...> end
"Will match"

卫语句中允许包含额外的条件:

iex> case {1, 2, 3} do
...>   {1, x, 3} when x > 0 ->
...>     "Will match"
...>   _ ->
...>     "Would match, if guard condition were not satisfied"
...> end
"Will match"

第一条语句在x是正数时才能匹配。

Elixir默认在卫语句中可以使用以下表达式:

- 比较运算符(==,!=,===,!==,>,>=,<,<=)
- 布尔运算符(and,or,not)
- 数学运算符(+,-,*,/)
- 一元运算符(+,-)
- 二进制连接符`<>`
- 当右边是一个范围或列表时,使用`in`操作符
- 下列的所有类型检查函数:
is_atom/1
is_binary/1
is_bitstring/1
is_boolean/1
is_float/1
is_function/1
is_function/2
is_integer/1
is_list/1
is_map/1
is_nil/1
is_number/1
is_pid/1
is_port/1
is_reference/1
is_tuple/1
- 加上这些函数
abs(number)
binary_part(binary, start, length)
bit_size(bitstring)
byte_size(bitstring)
div(integer, integer)
elem(tuple, n)
hd(list)
length(list)
map_size(map)
node()
node(pid | ref | port)
rem(integer, integer)
round(number)
self()
tl(list)
trunc(number)
tuple_size(tuple)

此外,用户可以定义自己的卫语句。例如,Bitwise模块使用函数和操作符来定义卫语句:bnot, ~~~, band, &&&, bor, |||, bxor, ^^^, bsl, <<<, bsr, >>>

注意,尽管例如andornot的布尔操作符允许在卫语句中使用,但更常用的短路操作符&&||!却不被允许。

记住,卫语句中的错误不会泄露,只是简单地让卫语句失败:

iex> hd(1)
** (ArgumentError) argument error
    :erlang.hd(1)
iex> case 1 do
...>   x when hd(x) -> "Won't match"
...>   x -> "Got: #{x}"
...> end
"Got 1"

如果没有语句匹配到,会抛出一个错误:

iex> case :ok do
...>   :error -> "Won't match"
...> end
** (CaseClauseError) no case clause matching: :ok

注意匿名函数也可以拥有多个卫语句:

iex> f = fn
...>   x, y when x > 0 -> x + y
...>   x, y -> x * y
...> end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> f.(1, 3)
4
iex> f.(-1, 3)
-3

匿名函数的分语句中参数的数量应当相同,否则会抛出错误。

iex> f2 = fn
...>   x, y when x > 0 -> x + y
...>   x, y, z -> x * y + z
...> end
** (CompileError) iex:1: cannot mix clauses with different arities in function definition

当你想要匹配不同的值时可以用case。然而,我们有时想要检查不同的情形并找出其中第一个结果为真的。这时,我们可以使用cond

iex> cond do
...>   2 + 2 == 5 ->
...>     "This will not be true"
...>   2 * 2 == 3 ->
...>     "Nor this"
...>   1 + 1 == 2 ->
...>     "But this will"
...> end
"But this will"

这和许多命令语言中的else if从句是一样的(虽然在这里不经常用到)。

如果没有一种情况返回为真,则抛出一个错误(CondClauseError)。所以,有必要在最后加上一个等于true的最终情况:

iex> cond do
...>   2 + 2 == 5 ->
...>     "This is never true"
...>   2 * 2 == 3 ->
...>     "Nor this"
...>   true ->
...>     "This is always true (equivalent to else)"
...> end
"This is always true (equivalent to else)"

最后,注意cond会将任何不是nilfalse的值认为真:

iex> cond do
...>   hd([1, 2, 3]) ->
...>     "1 is considered as true"
...> end
"1 is considered as true"

除了casecond,Elixir也提供了if/2unless/2这两个宏,让你在只需要检查一种情况时使用:

iex> if true do
...>   "This works!"
...> end
"This works!"
iex> unless true do
...>   "This will never be seen"
...> end
nil

如果传送给if/2的情况返回值是falsenildo/end中的代码就不会执行并只返回nilunless/2正相反。

它们也支持else块:

iex> if nil do
...>   "This won't be seen"
...> else
...>   "This will"
...> end
"This will"

注意:在这里if/2unless/2是被当作宏来执行的;而非其它许多语言中一样作为特殊的结构体。你可以在Kernel模块文档中查看说明文档和if/2的源代码。Kernel模块中定义了诸如+/2之类的操作符和'if_function/2`之类的函数,它们全都默认自动导入并在你的代码中可用。

目前,我们已经学习了四种控制结构:casecondifunless,它们都包含了do/end块。所以我们也能够以如下方式写if语句:

iex> if true, do: 1 + 2
3

注意在truedo:之间有一个逗号,这是因为Elixir中参数之间要以逗号隔开。我们称这种格式为关键字列表。我们也可以用关键字来传递else

iex> if false, do: :this, else: :that
:that

do/end块形式是在关键字形式的语法上经过化简所得的。这就是为什么do/end块形式不要求参数与块之间用逗号隔开。它消除了在书写块代码时的冗余信息。下列两种语法作用是相同的:

iex> if true do
...>   a = 1 + 2
...>   a + 10
...> end
13
iex> if true, do: (
...>   a = 1 + 2
...>   a + 10
...> )
13

需要记住的是在使用do/end块时,它们总是和最外层的函数调用捆绑在一起。例如:

iex> is_number if true do
...>  1 + 2
...> end
** (CompileError) undefined function: is_number/2

会被解释成:

iex> is_number(if true) do
...>  1 + 2
...> end
** (CompileError) undefined function: is_number/2

产生错误的原因是Elixir试图调用is_number/1函数,却得到了两个参数(if true表达式也会产生错误,因为if需要它的第二个参数,也就是do/end块)。添加明确的括号能够消除歧义:

iex> is_number(if true do
...>  1 + 2
...> end)
true

关键字列表在语言中扮演着重要的角色,在许多函数与宏中都很常见。在之后的章节中我们会继续他;探索它。现在,让我们谈谈“二进制,字符串和字符列表”。

教程来源于Github,感谢Jancd大佬的无私奉献,致敬!

技术教程推荐

硅谷产品实战36讲 -〔曲晓音〕

数据分析实战45讲 -〔陈旸〕

研发效率破局之道 -〔葛俊〕

图解 Google V8 -〔李兵〕

深度学习推荐系统实战 -〔王喆〕

说透区块链 -〔自游〕

Tony Bai · Go语言第一课 -〔Tony Bai〕

超级访谈:对话张雪峰 -〔张雪峰〕

手把手带你搭建推荐系统 -〔黄鸿波〕