case,cond和if

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

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

case

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

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

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

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

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

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

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

卫语句中的表达式

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

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

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

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

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

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

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

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

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

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

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

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

cond

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

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

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

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

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

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

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

ifunless

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

  1. iex> if true do
  2. ...> "This works!"
  3. ...> end
  4. "This works!"
  5. iex> unless true do
  6. ...> "This will never be seen"
  7. ...> end
  8. nil

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

它们也支持else块:

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

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

do/end

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

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

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

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

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

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

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

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

会被解释成:

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

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

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

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