查看源代码 模式匹配

在本章中,我们将学习为什么 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

这结束了我们对模式匹配的介绍。正如我们将在下一章中看到的,模式匹配在许多语言结构中非常常见,它们可以通过保护进一步增强。