Elixir入门教程-case,cond 和 if

  1. case
  2. 卫语句里的表达式
  3. cond
  4. if 和 unless
  5. do/end 块

在本章,我们将学习 case,cond 和 if 这几个控制流结构。

case

case允许我们将一个值和许多模式进行比较直到我们找到一个匹配的:

1
2
3
4
5
6
7
8
9
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"

如果你想模式匹配已经存在的变量,你需要使用 ^ 运算符:

1
2
3
4
5
6
7
iex> x = 1
1
iex> case 10 do
...> ^x -> "Won't match"
...> _ -> "Will match"
...> end
"Will match"

分支语句也允许用卫语句来指定额外的条件:

1
2
3
4
5
6
7
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,>>>。

注意,虽然布尔运算符,比如:and,or 和 not 运行在卫语句里使用,但是更加通用的运算符 &&,|| 和 ! 却不被允许在卫语句里使用。

切记:卫语句里的错误不会被抛出,而是使得卫语句失败:

1
2
3
4
5
6
7
iex> hd(1)
** (ArgumentError) argument error
iex> case 1 do
...> x when hd(x) -> "Won't match"
...> x -> "Got #{x}"
...> end
"Got 1"

如果没有一个分支匹配,则有错误抛出:

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

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

1
2
3
4
5
6
7
8
9
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

匿名函数每个分支的入参格式必须相同,否则有错误抛出:

1
2
3
4
5
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语句有用。然而,在许多情况中,我们想要检查不同的条件并且找到第一个为true的条件。在这样的场景下,可以使用cond:

1
2
3
4
5
6
7
8
9
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,这个条件将总是可以匹配到,这样的做法可能是必要的。

1
2
3
4
5
6
7
8
9
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外,任何值都是true:

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

if 和 unless

除了case和cond,Elixir也提供宏:if/2 和 unless/2 ,它们在你需要仅仅检查一个条件的时候很有用:

1
2
3
4
5
6
7
8
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之间的语句不会被执行,并且if/2的整体返回值是nil。unless/2的情况则相反。

它们也支持else块:

1
2
3
4
5
6
iex> if nil do
...> "This won't be seen"
...> else
...> "This will"
...> end
"This will"

注意:一个关于if/2和unless/2的有趣的注意事项是在Elixir里它们被实现为宏;它们不像在其他语言那样是一个特殊的语言结构。你可以参阅官方文档和Kernel模块的文档中if/2的说明。Kernel模块里也定义了像+/2这样的运算符和像is_function/2这样的函数,这些函数都默认地被自动导入并在你的代码中可用。

do/end块

到此,我们已经学了四种控制结构:case、cond、if 和 unless ,它们都包裹在 do/end 块里。我们也可以像下面这样写 if 控制结构:

1
2
iex> if true, do: 1 + 2
3

注意上面的例子在 true 和 do: 之间有一个逗号,这是因为它使用了Elixir的正规语法,每个参数用逗号分割。我们说这个语法是用关键字列表。我们也可以传递 else 使用关键字:

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

do/end是构建于关键字上的语法便利措施。这就是为什么在前面的参数和语句块之间不需要逗号。它的确非常有用,因为当写代码块的时候它删除了冗余。下面的例子是等效的:

1
2
3
4
5
6
7
8
9
10
iex> if true do
...> a = 1 + 2
...> a + 10
...> end
13
iex> if true, do: (
...> a = 1 + 2
...> a + 10
...> )
13

有一件事要记住,当你使用do/end的时候,它们总是和最外层函数调用绑定的。例如,下面的表达式:

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

将被解析为:

1
2
3
4
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 :

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

关键字列表Elixir里扮演很重要的角色,并且在许多函数和宏里普遍存在。在后续的章节里我们将进一步探索它们。接下来,我们讨论“二进制、字符串和字符列表”。

原文链接: http://elixir-lang.org/getting-started/case-cond-and-if.html