檢視原始碼 模式比對

在本章節中,我們將會學習為什麼 Elixir 中的 = 營運子被稱為比對營運子,以及如何在資料結構中使用它進行模式比對。我們將會學習用於存取先前繫結值的釘選營運子 ^

比對營運子

我們已經在 Elixir 中使用過 = 營運子幾次來指定變數

iex> x = 1
1
iex> x
1

在 Elixir 中,= 營運子實際上稱為比對營運子。讓我們看看原因

iex> x = 1
1
iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1

請注意 1 = x 是個有效的表達式,而且它會比對成功,因為左右兩邊都等於 1。當兩邊不匹配時,會引發 MatchError

變數只能指定在 = 的左側

iex> 1 = unknown
** (CompileError) iex:1: undefined variable "unknown"

模式比對

比對營運子不僅用於比對單純的值,它也可用於解構更複雜的資料類型。例如,我們可以在元組上進行模式比對

iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"

如果兩邊無法比對,例如元組具有不同的長度,則會發生模式比對錯誤

iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}

在比較不同類型時也會發生,例如在左側比對元組,在右側比對清單

iex> {a, b, c} = [:hello, "world", 42]
** (MatchError) no match of right hand side value: [:hello, "world", 42]

更有趣的是,我們可以在特定值上進行比對。以下範例聲明,當右側是一個以原子 :ok 開頭的元組時,左側才會比對右側

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}

我們可以在清單上進行模式比對

iex> [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex> a
1

清單也支援比對其自己的頭和尾

iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]

類似於 hd/1tl/1 函式,我們無法使用頭和尾模式比對空清單

iex> [head | tail] = []
** (MatchError) no match of right hand side value: []

[head | tail] 格式不僅用於模式比對,也用於將項目加到清單前面

iex> list = [1, 2, 3]
[1, 2, 3]
iex> [0 | list]
[0, 1, 2, 3]

模式比對讓開發人員可以輕易解構資料類型,例如元組和清單。正如我們將在後面的章節中看到的,它是 Elixir 中遞迴的基礎之一,也適用於其他類型,例如映射和二進位資料。

固定運算子

Elixir 中的變數可以重新繫結

iex> x = 1
1
iex> x = 2
2

不過,有時候我們不希望變數被重新繫結。

當你想要對變數的現有值進行模式比對,而不是重新繫結變數時,請使用固定運算子 ^

iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2

由於我們在 x 繫結到 1 的值時固定了它,因此它等於下列程式碼

iex> 1 = 2
** (MatchError) no match of right hand side value: 2

請注意,我們甚至看到完全相同的錯誤訊息。

我們可以在其他模式比對中使用固定運算子,例如元組或清單

iex> x = 1
1
iex> [^x, 2, 3] = [1, 2, 3]
[1, 2, 3]
iex> {y, ^x} = {2, 1}
{2, 1}
iex> y
2
iex> {y, ^x} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}

由於 x 在被固定時繫結到 1 的值,因此最後這個範例可以寫成

iex> {y, 1} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}

如果一個變數在模式中被提及多次,則所有參照都必須繫結到相同的值

iex> {x, x} = {1, 1}
{1, 1}
iex> {x, x} = {1, 2}
** (MatchError) no match of right hand side value: {1, 2}

在某些情況下,你不在乎模式中的特定值。將這些值繫結到底線 _ 是一種常見做法。例如,如果清單的頭對我們來說很重要,我們可以將尾繫結到底線

iex> [head | _] = [1, 2, 3]
[1, 2, 3]
iex> head
1

變數 _ 很特別,因為它永遠無法被讀取。嘗試從中讀取會產生編譯錯誤

iex> _
** (CompileError) iex:1: invalid use of _. "_" represents a value to be ignored in a pattern and cannot be used in expressions

儘管模式比對允許我們建立強大的結構,但它的用法受到限制。例如,你無法在比對的左側進行函式呼叫。下列範例無效

iex> length([1, [2], 3]) = 3
** (CompileError) iex:1: cannot invoke remote function :erlang.length/1 inside match

這結束了我們對模式比對的介紹。正如我們將在下一章中看到的,模式比對在許多語言結構中非常常見,它們可以進一步透過防護條件來擴充。