函数式语言的一大特点就是使用模式匹配来做条件判断。Elixir甚至可以使用模式匹配来替代 if else 这些传统编程语言的控制结构。模式匹配对于Elixir至关重要,那么到底什么是模式匹配呢?
赋值号?等号?模式匹配号?
稍微有一点编程经验的人都会知道“=”这个符号是赋值符号,左边表示一个变量,右边是一个表达式或者值,将右边的值或表达式求值的结果赋予左边的变量。刚开始学编程的时候,都会强调赋值号与数学中的等号的差别。
可是在elixir中,你又得忘掉编程中的赋值号,重新拾起遗忘的数学等号。因为Elixir并没有赋值的概念,“=”也不是赋值号。Elixir中的“=”号是一个模式匹配符号。
变量绑定
=
号是一个模式匹配符号,匹配成功将会进行绑定。请看下面的例子:
iex(1)> x = 1
1
iex(2)> x
1
iex(3)>
上述代码中,在x=1
这个表达式中,左边的x是一个变量(实际上很多函数式语言的变量都是不可变的量,elixir的“变量”也是不变的)。右边的 1 是一个数值。表达式的含义是进行变量绑定。将1的值绑定给变量x,所以输入x的时候,对x求值等于1。
也许你不以为然,变量绑定和赋值有什么差别呢?那么请看下面的语句:
iex(1)> x = 1
1
iex(2)> x
1
iex(3)> 1 = x
1
iex(4)> x
1
iex(5)>
常见的传统编程语言可不会出现1=x
这样的表达式。实际上,Elixir中的=
和数学上的等号概念类似,左边等于右边,右边等于左边,那么表达式就不会报错。当然,对于变量,可以重新绑定:
iex(1)> x = 1
1
iex(2)> x
1
iex(5)> x = 2
2
iex(6)> x
2
这个过程其实并没有改变变量x的值,而只是对x进行了重新的绑定。效果类似改变了值。
模式,匹配
既然知道了=
是模式匹配符号,而不是等号,那上面为什么在说变量绑定?Elixir中的模式匹配英文是Pattern Match。可以理解为通过某个模式进行匹配,就像使用正则表达式这样的pattern对字符串进行匹配一样。一旦匹配成功,就会对匹配的项进行变量绑定。从匹配到绑定整个过程称之为模式匹配。有点拗口,贵在理解啦。
模式匹配,就必须先知道模式。=
符号左边表示一个模式(pattern),右边则是一个表达式,或者一个数值。所谓的模式,也可以是一个数值,可以是一个变量,甚至也可以一些基本数据结构,例如列表(List),元组(Tuple),类字典(Dicionary)等。只要等号左右两边的数据类型和结构相同,则可以匹配。
从最简单的开始吧。前面的例子,我们见识了变量的模式匹配
iex(8)> a = 1
1
模式a和数值1匹配,匹配成功,将右边的数值和左边的模式变量a进行绑定。
iex(8)> a = 1
1
iex(9)> 1 = a
1
1=a
,这个模式匹配表示含义是,左边的模式是数字1。右边是一个变量,该变量已经绑定了数值1,因此对a变量(表达式)求值,得到值1。表达式实际含义等价于1 = 1
。右边的值1和左边的数字1值相等,匹配成功,然后进行变量绑定,可是右边的模式是数字1不是变量,就忽略了绑定过程。整个模式匹配成功。
在看下面的代码:
iex(8)> a = 1
1
iex(9)> 1 = a
1
iex(10)> 2 = a
** (MatchError) no match of right hand side value: 1
iex(10)>
表达式2=a
发生了MatchError错误。这里模式匹配不成功,因为 2 = 1
明显不成了,匹配失败。前面的例子中,变量进行了重绑定,实际上是进行了重新的模式匹配。
模式匹配本质是对等式求值的过程,例如数学的等式中 x = y + 1
。如果我们知道了y的值,自然可以通过数学表达式运算,求出x的值。模式匹配的本质就是针对右边的表达式或数字字面量求值,然后根据等式的原则,计算出左边的模式的值,再对左边进行变量的绑定。
模式匹配可以看成表达式运算,那么表达式肯定要是一类东西,而不是别的什么玩意了。例如x = y + 1
中的 x 和 y都是变量。如果是这样的表达式 fn x -> x end = y + 1
。左边是一个匿名函数式,显然函数是不能进行加减运算的。这也就是为什么左边的变量或表达式称之为模式。
复杂的例子
下面看几个复杂一点的模式匹配。
iex(12)> list = [1, 2, [3, 4, 5]]
[1, 2, [3, 4, 5]]
iex(13)> [a, b, c] = list
[1, 2, [3, 4, 5]]
iex(14)> a
1
iex(15)> b
2
iex(16)> c
[3, 4, 5]
iex(17)>
首先,有一个内嵌列表的列表。表达式list=[1, 2, [3, 4, 5]]
将列表和变量list进行匹配,这里列表可以看成一个整体,匹配成功,然后进行变量绑定,将变量list绑定成一个列表。
[a, b, c] = list
表达式左边的模式也是一个列表,右边也是一个列表(同一类型),并且左右两边的列表都是三个项目元素(同一结构)。匹配成功,然后进行变量绑定。
通过这个例子,可以清楚的知道,模式匹配的模式和表达式或数值的结构必须要相同。即同一数据类型,同一结构类型。
模式可以是数值和变量,也可以是变量和数值的混合结构
iex(17)> list = [1, 2, 3]
[1, 2, 3]
iex(18)> [a, 2, c] = list
[1, 2, 3]
iex(19)> a
1
iex(20)> c
3
iex(21)> 2
2
iex(22)>
例子中,右边的模式中的结构和右边一致 ,并且第二个元素都是2,匹配成功,剩下的a和c也可以匹配成功,进行变量绑定。
变量是可以重新绑定,但是在一次模式匹配中,变量只能绑定一次
iex(22)> [a, a] = [1, 1]
[1, 1]
iex(23)> [a, a] = [1, 2]
** (MatchError) no match of right hand side value: [1, 2]
iex(23)>
第二个表示中,含有两个a变量的列表在结构和数据类型都和左边相匹配,可是进行变量绑定的时候,a只能绑定一次,因此整个模式匹配过程失败。
想要重新绑定变量,只需要再进行一次模式匹配即可:
iex(23)> a = 1
1
iex(24)> [1, a] = [1, 2]
[1, 2]
iex(25)> a
2
iex(26)>
固定绑定
函数式本来追求的是不变,如果也想要变量绑定了之后不被重新绑定。可以使用脱字符^
将变量固定。该符号很形象,就像钉子一样固定了变量。
iex(2)> a = 1
1
iex(3)> a = 2
2
iex(5)> ^a = 3
** (MatchError) no match of right hand side value: 3
iex(5)> a
2
iex(6)>
忽略绑定
变量可以重新绑定,也可以固定绑定,当然还可以忽略绑定。与其他语言例如python,swift类似。Elixir可以使用下划线_
忽略变量的绑定。
iex(6)> [1, a] = [1, 2]
[1, 2]
iex(7)> a
2
iex(8)> [1, _] = [1, 2]
[1, 2]
_
就像占位符一样,虽然它总是能匹配成功,例如可以匹配任何值,可是它并不会绑定任何值。这个功能就是可以在模式匹配中,忽略一些不需要绑定的变量。
总结
以上就是模式匹配的基础内容。需要重新理解=
符号的含义,理解匹配这个过程。匹配的过程类似数学表达式的求值过程。
整个过程又和纯数学表达式求值有差别,因为模式只能放在左边,并左右的结构和类型都要一模一样。右边如果出现变量和表达式,则会先对右边的变量或表达式先求值。然后再和左边的模式进行匹配运算。
如果匹配成功,则对左边的模式(变量)进行变量绑定。一次模式匹配只能绑定一次变量。不同的模式匹配可以重新绑定变量。可以使用脱字符^
不让变量重新绑定,亦可以使用_
忽略绑定。
模式匹配介绍完毕,这个概念存在于很多函数式编程语言中。刚开始可能很不习惯,觉得和赋值没有差别。只要重新认识清楚数学上的等号的含义,就能慢慢的理解。当然实战才是根本,后面的将会使用到模式匹配,到时心中的疑问将会逐步解开。