4-模式匹配
==========
本章起教程進入 _不那么基礎的_ 階段,開始涉及函數式編程概念。
對之前沒有函數式編程經驗的人來說,這一章是一個基礎,需要好好學習和理解。
在Elixir中,```=```運算符實際上叫做 *匹配運算符*。
本章將講解如何使用```=```運算符來對各種數據結構進行模式匹配。
最后本章還會講解pin運算符(```^```),用來訪問某變量之前綁定的值。
## 4.1-匹配運算符
我們已經多次使用```=```符號進行變量的賦值操作:
```elixir
iex> x = 1
1
iex> x
1
```
在Elixir中,```=```作為 *匹配運算符*。下面來學習這樣的概念:
```elixir
iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1
```
注意```1 = x```是一個合法的表達式。
由于前面的例子給x賦值為1,因此在匹配時左右相同,所以它匹配成功了。而兩側不匹配的時候,MatchError將被拋出。
變量只有在匹配操作符```=```的左側時才被賦值:
```elixir
iex> 1 = unknown
** (RuntimeError) undefined function: unknown/0
```
錯誤原因是unknown變量沒有被賦過值,Elixir猜你想調用一個名叫```unknown/0```的函數,
但是找不到這樣的函數。
>
變量名在等號左邊,Elixir認為是賦值表達式;變量名放在右邊,Elixir認為是拿該變量的值和左邊的值做匹配。
## 4.2-模式匹配
匹配運算符不光可以匹配簡單數值,還能用來 *解構* 復雜的數據類型。例如,我們在元組上使用模式匹配:
```elixir
iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"
```
在兩端不匹配的情況下,模式匹配會失敗。比方說,匹配的兩端的元組不一樣長:
```elixir
iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}
```
或者兩端模式有區別(比如兩端數據類型不同):
```elixir
iex> {a, b, c} = [:hello, "world", "!"]
** (MatchError) no match of right hand side value: [:hello, "world", "!"]
```
利用“匹配”的這個概念,我們可以匹配特定值,或者在匹配成功時。
下面例子中先寫好了匹配的左端,它要求右端必須是個元組,且第一個元素是原子```:ok```。
```elixir
iex> {:ok, result} = {:ok, 13}
{:ok, 13}
iex> result
13
iex> {:ok, result} = {:error, :oops}
** (MatchError) no match of right hand side value: {:error, :oops}
```
用在列表上:
```elixir
iex> [a, 2, 3] = [1, 2, 3]
[1, 2, 3]
iex> a
1
```
列表支持匹配自己的```head```和```tail```
(這相當于同時調用```hd/1```和```tl/1```,給```head```和```tail```賦值):
```elixir
iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]
```
同```hd/1```和```tl/1```函數一樣,以上代碼不能對空列表使用:
```elixir
iex> [h|t] = []
** (MatchError) no match of right hand side value: []
```
>
[head|tail]這種形式不光在模式匹配時可以用,還可以用作向列表插入前置數值:
```elixir
iex> list = [1, 2, 3]
[1, 2, 3]
iex> [0|list]
[0, 1, 2, 3]
```
模式匹配使得程序員可以容易地解構數據結構(如元組和列表)。
在后面我們還會看到,它是Elixir的一個基礎,對其它數據結構同樣適用,比如圖和二進制。
小結:
* 模式匹配使用```=```符號
* 匹配中等號左右的“模式”必須相同
* 變量在等號左側才會被賦值
* 變量在右側時必須有值,Elixir拿這個值和左側相應位置的元素做匹配
## 4.3-pin運算符
在Elixir中,變量可以被重新綁定:
```elixir
iex> x = 1
1
iex> x = 2
2
```
>Elixir可以給變量重新綁定(賦值)。
它帶來一個問題,就是對一個單獨變量(而且是放在左端)做匹配時,
Elixir會認為這是一個重新綁定(賦值)操作,而不會當成匹配,執行匹配邏輯。
這里就要用到pin運算符。
如果你不想這樣,可以使用pin運算符(^)。
加上了pin運算符的變量,在匹配時使用的值是本次匹配前就賦予的值:
```elixir
iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
iex> {x, ^x} = {2, 1}
{2, 1}
iex> x
2
```
注意如果一個變量在匹配中被引用超過一次,所有的引用都應該綁定同一個模式:
```elixir
iex> {x, x} = {1, 1}
1
iex> {x, x} = {1, 2}
** (MatchError) no match of right hand side value: {1, 2}
```
有些時候,你并不在意模式匹配中的一些值。
可以把它們綁定到特殊的變量 “ _ ” (underscore)上。
例如,如果你只想要某列表的head,而不要tail值。你可以這么做:
```elixir
iex> [h | _ ] = [1, 2, 3]
[1, 2, 3]
iex> h
1
```
變量“ _ ”特殊之處在于它不能被讀,嘗試讀取它會報“未綁定的變量”錯誤:
```elixir
iex> _
** (CompileError) iex:1: unbound variable _
```
盡管模式匹配看起來如此牛逼,但是語言還是對它的作用做了一些限制。
比如,你不能讓函數調用作為模式匹配的左端。下面例子就是非法的:
```elixir
iex> length([1,[2],3]) = 3
** (CompileError) iex:1: illegal pattern
```
模式匹配介紹完了。 在以后的章節中,模式匹配是常用的語法結構。