case, cond, and if

在本章中,我们将了解casecond以及if控制流结构。

case

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"

条款还允许通过 guards 指定额外的条件:

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肯定时才会匹配。

请记住 guards 的错误不会泄漏,而只是让 guards 失败:

iex> hd(1)
** (ArgumentError) argument error
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

有关guards的更多信息,如何使用它们以及允许使用哪些表达式,请参阅guards的完整文档

注意匿名函数也可以有多个子句和警卫:

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

cond

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许多命令式语言中的子句(尽管在此使用频率较低)。

如果没有条件返回true,则会引发错误(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考虑除了nil并且false是真实的任何价值:

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

ifunless

除了casecond,药剂还提供了宏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返回的条件false或者nil给定的主体之间的do/end条件没有被执行,而是返回nil。恰恰相反unless/2

他们也支持else块:

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

注意:关于if/2和有趣的一点unless/2是,它们在语言中被实现为宏; 它们不是特殊的语言结构,因为它们会以多种语言显示。您可以查看文档和源if/2中的Kernel模块文档。该Kernel模块也是运营商喜欢+/2和功能is_function/2定义的地方,默认情况下全部自动导入并在您的代码中可用。

do/end砌块

在这一点上,我们已经学会了四种控制结构:casecondif,和unless,和他们都裹着do/end块。发生这种情况我们也可以这样写if

iex> if true, do: 1 + 2
3

注意上面的例子在true和之间有一个逗号do:,这是因为它使用了 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

这导致未定义的函数错误,因为该调用传递两个参数,并且is_number/2不存在。这个if true表达本身是无效的,因为它需要这个块,但由于这个元素is_number/2不匹配,Elixir甚至没有达到它的评价。

添加明确的括号足以将该块绑定到if

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

关键字列表在语言中扮演着重要角色,并且在许多功能和宏中很常见。我们将在未来的章节中进一步探讨它们。现在是讨论“二进制文件,字符串和字符列表”的时候了。