5-流程控制
==========
[case](#51-case)
[衛兵子句中的表達式](#52-%E5%8D%AB%E5%85%B5%E5%AD%90%E5%8F%A5%E4%B8%AD%E7%9A%84%E8%A1%A8%E8%BE%BE%E5%BC%8F)
[cond](#53-cond)
[if和unless](#54-if%E5%92%8Cunless)
[do語句塊](#55-do%E8%AF%AD%E5%8F%A5%E5%9D%97)
本章講解case,cond和if的流程控制結構。
## 5.1-case
case將一個值與許多模式進行匹配,直到找到一個匹配成功的:
```elixir
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
```
如果與一個已賦值的變量做比較,要用pin運算符(^)標記該變量:
```elixir
iex> x = 1
1
iex> case 10 do
...> ^x -> "Won't match"
...> _ -> "Will match"
...> end
```
可以加上衛兵子句(guard clauses)提供額外的條件:
```elixir
iex> case {1, 2, 3} do
...> {1, x, 3} when x > 0 ->
...> "Will match"
...> _ ->
...> "Won't match"
...> end
```
于是上面例子中,第一個待比較的模式多了一個條件:x必須是正數。
## 5.2-衛兵子句中的表達式
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)
- 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)
記住,衛兵子句中出現的錯誤不會漏出,只會簡單地讓衛兵條件失敗:
```elixir
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"
```
如果case中沒有一條模式能匹配,會報錯:
```elixir
iex> case :ok do
...> :error -> "Won't match"
...> end
** (CaseClauseError) no case clause matching: :ok
```
匿名函數也可以像下面這樣,用多個模式或衛兵條件來靈活地匹配該函數的參數:
```elixir
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
```
需要注意的是,所有case模式中表示的參數個數必須一致,否則會報錯。
上面的例子兩個待匹配模式都是x,y。如果再有一個模式表示的參數是x,y,z,那就不行:
```elixir
iex(5)> f2 = fn
...(5)> x,y -> x+y
...(5)> x,y,z -> x+y+z
...(5)> end
** (CompileError) iex:5: cannot mix clauses with different arities in function definition
(elixir) src/elixir_translator.erl:17: :elixir_translator.translate/2
```
## 5.3-cond
case是拿一個值去同多個值或模式進行匹配,匹配了就執行那個分支的語句。
然而,許多情況下我們要檢查不同的條件,找到第一個結果為true的,執行它的分支。
這時我們用cond:
```elixir
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,會報錯。因此,實際應用中通常會使用true作為最后一個條件。
因為即使上面的條件沒有一個是true,那么該cond表達式至少還可以執行這最后一個分支:
```elixir
iex> cond do
...> 2 + 2 == 5 ->
...> "This is never true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> true ->
...> "This is always true (equivalent to else)"
...> end
```
用法就好像許多語言中,switch語句中的default一樣。
最后需要注意的是,cond視所有非false和nil的值為true:
```elixir
iex> cond do
...> hd([1,2,3]) ->
...> "1 is considered as true"
...> end
"1 is considered as true"
```
## 5.4 if和unless
除了case和cond,Elixir還提供了兩很常用的宏:```if/2```和```unless/2```,
用它們檢查單個條件:
```elixir
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語句塊:
```elixir
iex> if nil do
...> "This won't be seen"
...> else
...> "This will"
...> end
"This will"
```
>有趣的是,```if/2```和```unless/2```是以宏的形式提供的,而不像在很多語言中那樣是語句。
可以閱讀文檔或```if/2```的源碼
([Kernel模塊](http://elixir-lang.org/docs/stable/elixir/Kernel.html))。
_Kernel_ 模塊還定義了諸如```+/2```運算符和```is_function/2```函數。
它們默認被導入,因而在你的代碼中可用。
## 5.5-```do```語句塊
以上講解的4種流程控制結構:case,cond,if和unless,它們都被包裹在do/end語句塊中。
即使我們把if語句寫成這樣:
```elixir
iex> if true, do: 1 + 2
3
```
在Elixir中,do/end語句塊方便地將一組表達式傳遞給```do:```。以下是等同的:
```elixir
iex> if true do
...> a = 1 + 2
...> a + 10
...> end
13
iex> if true, do: (
...> a = 1 + 2
...> a + 10
...> )
13
```
我們稱第二種語法使用了 **關鍵字列表(keyword lists)**。我們可以這樣傳遞```else```:
```elixir
iex> if false, do: :this, else: :that
:that
```
注意一點,do/end語句塊永遠是被綁定在最外層的函數調用上。例如:
```elixir
iex> is_number if true do
...> 1 + 2
...> end
```
將被解析為:
```elixir
iex> is_number(if true) do
...> 1 + 2
...> end
```
這使得Elixir認為你是要調用函數```is_number/2```(第一個參數是if true,第二個是語句塊)。
這時就需要加上括號解決二義性:
```elixir
iex> is_number(if true do
...> 1 + 2
...> end)
true
```
關鍵字列表在Elixir語言中占有重要地位,在許多函數和宏中都有使用。后文中還會對其進行詳解。