Elixir入门教程-模式匹配

  1. 匹配运算符
  2. 模式匹配
  3. pin运算符

在本章,我们将展示给大家 = 运算符在Elixir里如何实际上是一个匹配运算符的以及如何用它来模式匹配数据结构里的数据。最后,我们将学习pin运算符 ^,它被用来访问变量之前被绑定的值。

匹配运算符

我们已经用过几次 = 运算符在Elixir里给变量赋值:

1
2
3
4
iex> x = 1
1
iex> x
1

在Elixir里,= 运算符实际上被称为匹配运算符。让我们来看看究竟:

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

我们注意到 1 = x 是一个有效表达式,而它匹配的原因是因为左右两边都等于1。当两边不匹配的时候,一个MatchError错误抛出。

变量被赋值的时候只能够处于 = 运算符的左边:

1
2
iex> 1 = unknown
** (CompileError) iex:1: undefined function unknown/0

因为之前没有定义unknown变量,Elixir就假设你是想尝试调用 unknown/0 这个函数,但是这样的函数并不存在。

模式匹配

匹配运算符不仅是用来匹配简单的值,而且对于解构更复杂的数据类型也是有用的。例如,我们可以在元组上进行模式匹配:

1
2
3
4
5
6
iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"

在两边不匹配的情况下模式匹配将出错。例如下面的例子,当元组有不同的大小的时候:

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

或者两边的数据类型不一样:

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

更有趣地是,我们可以在指定的值上进行匹配。比如下面的例子就断言当右边是一个以原子 :ok 开始的元组的时候左边才和右边匹配:

1
2
3
4
5
6
7
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}

我们也可以在列表上模式匹配:

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

列表也支持匹配它的头部和尾部:

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

像 hd/1 和 tl/1 函数一样,我们不能用一个头部和尾部的模式来匹配一个空列表:

1
2
iex> [h | t] = []
** (MatchError) no match of right hand side value: []

[head | tail] 格式不仅用在模式匹配而且也可以用在给列表加元素上:

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

模式匹配允许开发者很容易地解构诸如元组和列表这样的数据类型。正如我们将在接下来的章节看到的,这是Elixir递归的基础之一,并且也应用于像map和二进制数据等其他类型。

pin运算符

Elixir的变量可以重新绑定:

1
2
3
4
iex> x = 1
1
iex> x = 2
2

当你想模式匹配已经存在的变量的值而不是重新绑定这个变量的时候,pin运算符 ^ 就有用处了:

1
2
3
4
5
6
7
8
9
10
iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
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}

因为我们已经将1赋值给变量x,上述最后一个表达式也可以写成下面一样:

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

如果一个变量在一个模式里出现了不止一次,则所有对它的引用都应该绑定到相同的模式:

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

在一些场景里,你不关心模式里的某个特别的值。通用的做法是将这些值绑定到下划线,_ 。例如,如果仅是列表头部是我们需要的,我们可以将尾部赋给下划线:

1
2
3
4
iex> [h | _] = [1, 2, 3]
[1, 2, 3]
iex> h
1

变量 _ 在这里很特别,它永远不会被读取。如果尝试取读取它,会得到一个 未绑定变量 的错误:

1
2
iex> _
** (CompileError) iex:1: unbound variable _

虽然模式匹配允许我们构建非常有用的结构,但是它的用法是有限制的。例如,你不能在匹配的左边调用函数。下面的例子就是无效的:

1
2
iex> length([1, [2], 3]) = 3
** (CompileError) iex:1: illegal pattern

到此就完成了我们的模式匹配介绍。正如我们将在下一章看到的,模式匹配在Elixir的许多语言构造里是非常普遍的。

原文链接: http://elixir-lang.org/getting-started/pattern-matching.html