Pattern Matching 的意义

744 阅读6分钟
原文链接: mp.weixin.qq.com

昨天的文章重发。写这篇文章的时候,脑子里在构思另一篇 Type System 的文章,结果就抽风把 Pattern Matching 一路写成了 Type Matching,多亏有读者指出,特做更正后重发,深表歉意。以下为正文:

一个语言特性往往具备多种 API 表现形式,理解特性比硬记 API 更有助于我们掌握一门新语言。Swift 作为现代编程语言的集大成者,具备很多优秀的特性来帮助开发者快速高效的编写代码,Pattern Matching 就是其中之一。

程序员 vs 编程语言

我们所写的代码,或者说一个 App 所具备的功能,最终都是程序员和编程语言共同作用的成果。我们使用某种编程语言写代码的时候,会受语言设计者所制定的各种规则制约,这些制约背后都隐含着数十年行业经验的积累,和设计者高明的设计技巧。理解这些规则所带来的好处和设计值的良苦用心,能在大大小小的方面,让我们的代码更加可靠和稳定。

前段时间,「Clean Code」的作者,Bob 大叔,在博客里吐槽了 Swift 这类新型编程语言可能会将开发者带入「Dark Path」。文章举了不少例子,大意是说以 Swift 为代表的新语言正在尝试替开发者做更多的事情, 编程语言做的越多,开发者自然就做的越少,看上去是件好事,毕竟开发者更容易犯错,但 Bob 大叔认为规则是死的,人是活的,程序员应该在代码的编写中承担更大的责任,而且有些生硬的规则会反过来会给代码的表达产生负面效果。这个观点显得有些偏激,也引起了不小的争论,对错姑且不论,对于我们开发者来说,至少要搞明白 Swift 这类新语言所包含的规则背后的意义,才能既合理的使用规则又不被规则所制,写出漂亮的代码。

Bob 大叔博客地址:blog.cleancoder.com

Pattern Matching

Pattern Matching 早在 Scala, Haskell 这些语言中就存在,作为编程语言的特性,旨在替程序员分担一部分的工作。顾名思义,Pattern Matching 可以替我们解决类型匹配的的问题,这简单的一句话,在一些不同的场景下,有着不同的表现形式,表现形式一多,又会让简单的概念理解起来变得复杂。对于复杂概念的理解,我们最好能用一句话做高度精炼简洁的概括,以不变应万变。

Pattern Matching 简单来说,就是编程语言替我们程序员节省了一件事,这件事可以用两个单词来描述:Check 和 Extract。

Check 是指检查条件是否满足,类型是否匹配。这么说有些抽象,我们可以把 Check 想象成一个函数,这个函数接收两个参数,参数一是源数据,参数二是目标数据,返回值是一个 Bool 值,Bool 值表示两个参数之间的某种关系是否成立,至于这个关系具体指什么,就因场景而异了。我们可以用如下一个函数来表达:

func check(targetData: [Type1], sourceData: [Type2]) -> Bool  {
   //check relation, return true or false.
}

源数据和目标数据可以是不同的类型,只要他们之间满足某种关系,关系的定义取决于 check 函数内部的实现。系统默认替我们实现了一些关系,也允许我们通过操作符重载的方式自定义关系。看几个简单的例子:

var result: String? = getResult()
switch result {
case .none:    
   print("no result")
case let r:    
   print("result is \(r)") }

上面是 Pattern Matching 在 switch 当中的应用。源数据是 result,type 为 optional。目标数据是 .none 和 r,type 为具体的 String。二者之间的关系是个等式关系,这个关系默认由系统提供。这个关系说白了,无非是一个 if,else 语句,来判断 result 到底是 .none 还是具体的值。

当然我们也可以直接写 if,else 来替代 Pattern Matching,在 Objective C 中,没有 Pattern Matching,我们确实是用 if,else 来做 check 的。但明显使用 Pattern Matching 我们可以写更少的代码,同时让代码更容易阅读,表达更清晰,这也是 Swift 中引入 Pattern Matching 的原意所在。

Pattern Matching 除了做 check 之外,很多时候也被用来做 extract。Extract 是提取数据的意思,Pattern Matching 配合其他关键字就可以完成数据的 extract。比如上面的代码中,switch 配合  case let r: 就将 result 中所包含的值提取到了 r 中。严格来说第一步还是 check,check 关系满足之后,再做 extract。

Pattern Matching 虽然表现形式繁多,但最后都会落实到 check 和 extract 这两个单词之上,大家可以尝试去发掘下其他 Swift 语境下 Pattern Matching 是如何做 check 和 extract 的。

深入了解过 Pattern Matching 的同学应该知道,其实 Pattern Matching 本质上就是一个特殊的操作符:~=。我们可以把这个操作符等同于我们上面提到的 check 函数。当我们针对自己定义的数据类型做 ~= 的操作符重载之后,就可以实现自定义规则的 Pattern Matching 了。

再换句话来描述 Pattern Matching:检查两个数据之间是否满足某种关系。

Pattern Matching 并不是 Swift 所独有,但在 Swift 语境下,主要是通过 case 这个关键字来体现。在 Objective C 的语境下,case 是和 switch 搭配使用,和 if 的含义接近,而在 Swift 中 case 关键字具备了 Pattern Matching 的含义之后,case 关键字可以在更多的场景下来使用,可以和更多其他的关键字配合,以延伸 Pattern Matching 的使用场景。除了 switch case 之外,我们还可以用 for case,while case,if case,guard case,无论关键字如何搭配,其本意都是在做 Pattern Matching。这也是为什么初次接触 Swift 会感觉语法太过灵活且不易掌握的原因。

总结

希望这篇文章能帮助一些朋友简单点理解 Pattern Matching 的意义所在。另一个目的是介绍一个学习小技巧:对于新知识点的学习,记 API 或者编程语言的各种表现形式,不如去理解其背后的设计思想。不过总结和提炼思想,往往需要花费更多的时间和精力,回报是对知识的掌握更深刻和牢固。