5-流程控制

    本章讲解case,cond和if的流程控制结构。

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

    如果与一个已赋值的变量做比较,要用pin运算符(^)标记该变量:

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

    可以加上卫兵子句(guard clauses)提供额外的条件:

    1. iex> case {1, 2, 3} do
    2. ...> {1, x, 3} when x > 0 ->
    3. ...> "Will match"
    4. ...> _ ->
    5. ...> "Won't match"
    6. ...> end

    于是上面例子中,第一个待比较的模式多了一个条件:x必须是正数。

    Erlang中只允许以下表达式出现在卫兵子句中:

    • 比较运算符(==,!=,===,!==,>,<,<=,>=)
    • 布尔运算符(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_number/1
      • is_pid/1
      • is_reference/1
      • is_tuple/1
    • 外加以下函数:
      • abs(number)
      • bit_size(bitstring)
      • byte_size(bitstring)
      • div(integer, integer)
      • elem(tuple, n)
      • length(list)
      • map_size(map)
      • node()
      • node(pid | ref | port)
      • rem(integer, integer)
      • round(number)
      • self()
      • tl(list)
      • trunc(number)
      • tuple_size(tuple)

    记住,卫兵子句中出现的错误不会漏出,只会简单地让卫兵条件失败:

    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"

    如果case中没有一条模式能匹配,会报错:

    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

    需要注意的是,所有case模式中表示的参数个数必须一致,否则会报错。
    上面的例子两个待匹配模式都是x,y。如果再有一个模式表示的参数是x,y,z,那就不行:

    case是拿一个值去同多个值或模式进行匹配,匹配了就执行那个分支的语句。
    然而,许多情况下我们要检查不同的条件,找到第一个结果为true的,执行它的分支。
    这时我们用cond:

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

    这样的写法和命令式语言里的else if差不多一个意思(尽管很少这么写)。

    如果没有一个条件结果为true,会报错。因此,实际应用中通常会使用true作为最后一个条件。
    因为即使上面的条件没有一个是true,那么该cond表达式至少还可以执行这最后一个分支:

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

    用法就好像许多语言中,switch语句中的default一样。

    最后需要注意的是,cond视所有非false和nil的值为true:

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

    除了case和cond,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的条件结果为false或者nil,那么它在do/end间的语句块就不会执行,
    该表达式返回nil。unless/2相反。

    1. iex> if nil do
    2. ...> else
    3. ...> "This will"
    4. ...> end
    5. "This will"

    以上讲解的4种流程控制结构:case,cond,if和unless,它们都被包裹在do/end语句块中。
    即使我们把if语句写成这样:

    在Elixir中,do/end语句块方便地将一组表达式传递给do:。以下是等同的:

    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

    我们称第二种语法使用了 关键字列表(keyword lists)。我们可以这样传递else

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

    注意一点,do/end语句块永远是被绑定在最外层的函数调用上。例如:

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

    将被解析为:

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

    这使得Elixir认为你是要调用函数is_number/2(第一个参数是if true,第二个是语句块)。
    这时就需要加上括号解决二义性:

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

    关键字列表在Elixir语言中占有重要地位,在许多函数和宏中都有使用。后文中还会对其进行详解。