Skip to content

四火的唠叨

一个纯正程序员的啰嗦

Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接
Menu

Scala 的模式匹配

Posted on 09/21/201506/23/2019 by 四火

scala

最近开始学习 Scala,相较于学习 Haskell 的过程来看,Scala 真是直观得多,友好得多,更容易上手。以前写过关于从熟悉的 Java 和 JavaScript 来逐步学习 Groovy 和 Haskell 的文章,这以后再来学习 Scala 的话,就可以不断比较了。如果和我一样有 Java 经验的话但是从来没有接触过 Scala 的话,建议先阅读这篇文章,A Scala Tutorial for Java Programmers,一边比较,一边熟悉,同时配套的还有这个,Scala for Java programmers – Joakim Ohlrogge & Enno Runne,Youtube 上的视频,很直观,然后再从 Scala 官网的文档上面逐步涉入。

这里的模式匹配可能是历经函数式编程才引入的概念,是广泛存在于编程语言函数使用中的,而并非以前接触的 “正则表达式” 这样仅仅用于字符串处理的特性。在此之前,先来看看 Haskell 中的模式匹配,我在这里曾经举过这个阶乘的例子:

1
2
3
factorial :: (Integral a) => a -> a 
factorial 0 = 1
factorial n = n * factorial (n - 1)

根本不需要多余的解释,一眼就看懂。模式匹配在这里起到了 if-else 的作用,对于逻辑的执行,起到了一个 “变化点” 的作用。在以往传统的静态语言中,要在程序中植入 “变化点”,要么就是 if-else 语句(本质上 switch-case 和使用 Map 去寻找匹配的 value 也属于 if-else),要么就是多态,要么就是方法重载。现在我们看到了一个根据参数改变程序执行逻辑步骤的新武器。虽然说,这个例子可以说和使用 if-else 相比,似乎没有太大的区别,但是在存在不同的参数组合情况的时候,这个写法的优势就体现出来了:

1
2
3
translate :: String -> String
translate ('$':x) = "Dollar: " ++ x
translate (_:x) = "Unknown: " ++ x

其中的下划线 “_” 就是通配符,这种写法上的 pattern 很像带有 default 语句的 switch-case,最后一个通配符保证了不会有异常抛出,所有 case 都被涵盖。

再挪到 Scala 里面看模式匹配,上面的情况也都能够支持。模式匹配可不一定只作用在单个参数作为整体来实现匹配,参数还可以拆分,比如说:

1
List(1,2,3) match{ case List(_,_,3) => println("ok") }

这就是忽略了前两个参数,直接比对第三个参数是否为 3。当然,除了上面的情形,模式匹配还可以匹配参数的类型。

不止作用在参数的级别上,还可以作用在类和对象的级别上,比如 Scala 官网首页上面的这个例子:

1
2
3
4
5
6
7
8
9
// Define a set of case classes for representing binary trees.
sealed abstract class Tree
  case class Node(elem: Int, left: Tree, right: Tree) extends Tree
  case object Leaf extends Tree
// Return the in-order traversal sequence of a given tree.
def inOrder(t: Tree): List[Int] = t match {
  case Node(e, l, r) => inOrder(l) ::: List(e) ::: inOrder(r)
  case Leaf          => List()
}

Tree 本身可以有两种类型的实现,一种是 Node,它是个类,接受本身的值、左子树、右子树这三个构造参数;另一种是 Leaf,就是一个叶子实例(不是类)。那么在实现中序遍历的 inOrder 方法的时候,如果是分支节点,那么就递归执行中序遍历的方法(左子树-> 节点自己-> 右子树),然后把着三个结果 List 拼接起来;否则对于叶子节点,就创建一个空的 List。

在我们的印象中,传统语言的多态实现,一定是基于 “类和对象” 的,换言之,在运行时才能确定执行某一个接口(或者抽象类)方法的实体到底是谁(哪个对象)。但是在这里的模式匹配上,这个变化点被移到了函数(或者说方法)上,看起来实现的功能是类似的,但是二者各有优劣:

  • 如果使用传统的多态方式,思维基于类和对象,方法只是某一类或对象的附庸,方法本身单独存在并无意义,因此如果增加了某一个新的实现类,那么我需要把这个新实现类中需要重载/实现接口(或抽象类)的放的所有方法全部实现一遍,而这些增加的方法都是集中在这个新增的类/对象里的。比如说,如果写 Java 代码去实现上述类似的功能,我可以定义一个接口 Tree,内有方法 inOrder,然后再分别定义实现类 Node 和 Leaf,去实现这个接口。这种方式对于新增一个类的时候,显得直观、内聚,所有的代码都在新增加的那个类里面,符合了开闭原则。但是,如果是要在接口中新增一个方法的话,就完蛋了,就是所谓的 “要改接口”,还得把所有的子类实现全部修改一遍。在 Java 8 中,为了 Lambda 表达式这个特性,给一些以往所谓的纯粹的、不含逻辑的接口,引入了 “函数接口” 的概念——被允许存在 “一个非 java.lang.Object 中定义过的抽象的方法”,这个看起来有点像抽自己脸的行为(最初对 “接口” 这个概念的定义,是要求它 “纯粹”,没有任何方法实现),正是由于上面说的这个原因造成的——接口不具备开放修改的能力,如今要在接口中增加一个默认行为,又要保持向后兼容性,还没有 Trait 之类的嫁接别处功能的特性,就只能用这种奇怪的路子来实现了。
  • 相反,模式匹配使得关注的核心点变成了函数本身,函数变成了一等公民,它可以脱离类和对象的附庸而独立存在了。如果要增加某一类或者对象,就变成了特别麻烦的事情,要修改现有的所有相关函数,增加一个 case 分支;但如果要给某一类类和对象增加一个方法,只需要修改一处即可(上面例子中,如果我想增加先序遍历的逻辑,只需要实现 “preOrder” 一个函数即可),而这个增加的函数内部是内聚的,增加这个修改符合开闭原则。因此,二者各有利弊,要看设计和使用场景。

上面的这些模式匹配方式组合起来,可以执行一些复杂的匹配,比如基于构造器:

1
case Node(_, Node(1,_,_), Node(2,_,_))

这样的,是要求构造器的三个参数中,左子树参数的值是 1,右子树参数是 2。

甚至可以这样:

1
case Node(_, nodeToReturn@Node(1,_,_), Node(1,_,_)) => nodeToReturn

表示碰到这个 case 的时候,返回构造器的第二个参数。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

你可能也喜欢看:

  1. 一段集合操作的不同语言表达
  2. 多重继承的演变
  3. Good Surrounding For Programmers
  4. 生活不止眼前的苟且,还有诗和远方
  5. 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(元编程)

1 thought on “Scala 的模式匹配”

  1. lcn says:
    09/21/2015 at 1:34 AM

    比较 patten matching 和 subtype polymorphism 没提到 the expression problem?

    Reply

Leave a Reply to lcn Cancel reply

Your email address will not be published. Required fields are marked *

订阅·联系

四火,啰嗦的程序员一枚,现居西雅图

Amazon Google Groovy Hadoop Haskell Java JavaScript LeetCode Oracle Spark 互联网 亚马逊 前端 华为 历史 同步 团队 图解笔记 基础设施 工作 工作流 工具 工程师 应用系统 异步 微博 思考 技术 数据库 曼联 测试 生活 眼界 程序员 管理 系统设计 缓存 编程范型 美股 英语 西雅图 设计 问题 面向对象 面试

分类

  • Algorithm and Data Structure (30)
  • Concurrency and Asynchronization (6)
  • System Architecture and Design (43)
  • Distributed System (18)
  • Tools Frameworks and Libs (13)
  • Storage and Data Access (8)
  • Front-end Development (33)
  • Programming Languages and Paradigms (55)
  • Testing and Quality Assurance (4)
  • Network and Communication (6)
  • Authentication and Authorization (6)
  • Automation and Operation Excellence (13)
  • Machine Learning and Artificial Intelligence (6)
  • Product Design (7)
  • Hiring and Interviews (14)
  • Project and Team Management (14)
  • Engineering Culture (17)
  • Critical Thinking (25)
  • Career Growth (57)
  • Life Experience and Thoughts (45)

推荐文章

  • 谈谈分布式锁
  • 常见分布式系统设计图解(汇总)
  • 系统设计中的快速估算技巧
  • 从链表存在环的问题说起
  • 技术面试中,什么样的问题才是好问题?
  • 从物理时钟到逻辑时钟
  • 近期面试观摩的一些思考
  • RSA 背后的算法
  • 谈谈 Ops(汇总 + 最终篇):工具和实践
  • 不要让业务牵着鼻子走
  • 倔强的程序员
  • 谈谈微信的信息流
  • 评审的艺术——谈谈现实中的代码评审
  • Blog 安全问题小记
  • 求第 K 个数的问题
  • 一些前端框架的比较(下)——Ember.js 和 React
  • 一些前端框架的比较(上)——GWT、AngularJS 和 Backbone.js
  • 工作流系统的设计
  • Spark 的性能调优
  • “残酷” 的事实
  • 七年工作,几个故事
  • 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  • 一道随机数题目的求解
  • 层次
  • Dynamo 的实现技术和去中心化
  • 也谈谈全栈工程师
  • 多重继承的演变
  • 编程范型:工具的选择
  • GWT 初体验
  • java.util.concurrent 并发包诸类概览
  • 从 DCL 的对象安全发布谈起
  • 不同团队的困惑
  • 不适合 Hadoop 解决的问题
  • 留心那些潜在的系统设计问题
  • 再谈大楼扔鸡蛋的问题
  • 几种华丽无比的开发方式
  • 我眼中的工程师文化
  • 观点的碰撞
  • 谈谈盗版软件问题
  • 对几个软件开发传统观点的质疑和反驳
  • MVC 框架的映射和解耦
  • 编程的未来
  • DAO 的演进
  • 致那些自嘲码农的苦逼程序员
  • Java 多线程发展简史
  • 珍爱生命,远离微博
  • 网站性能优化的三重境界
  • OSCache 框架源码解析
  • “ 你不适合做程序员”
  • 画圆画方的故事

近期评论

  • panshenlian.com on 初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow
  • panzhixiang on 关于近期求职的近况和思考
  • Anonymous on 闲聊投资:亲自体验和护城河
  • 四火 on 关于近期求职的近况和思考
  • YC on 关于近期求职的近况和思考
  • mafulong on 常见分布式基础设施系统设计图解(四):分布式工作流系统
  • 四火 on 常见分布式基础设施系统设计图解(八):分布式键值存储系统
  • Anonymous on 我裸辞了
  • https://umlcn.com on 资源链接
  • Anonymous on 我裸辞了
© 2025 四火的唠叨 | Powered by Minimalist Blog WordPress Theme
Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接