HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

Scala 模式匹配的亮点——Martin Odersky 访谈(四)

  • 2016-01-20
  • 本文字数:3601 字

    阅读完需:约 12 分钟

Martin Odersky 向 Bill Venners 和 Frank Sommers 谈论 Scala 模式匹配的机制和目的。

Scala 是一种新兴的通用用途、类型安全的 Java 平台语言,结合了面向对象和函数式编程。它是洛桑联邦理工大学教授 Martin Odersky 的心血结晶。本访谈系列由多部分组成,由 Artima 网站的 Frank Sommers 和 Bill Venners 向 Martin Odersky 讨教 Scala。在第一部分 Scala 起源中(点击查看《Scala 起源》中文翻译),Odersky 讲述了导致Scala 诞生的那些历史。在第二部分 Scala 的设计目标中,它讨论了 Scala 设计中的妥协、目标、创新和优势。在第三部分 Scala 类型系统的目的中,他挖掘了 Scala 的类型系统的设计动机。本期是第四部分,也是最后一部分,Odersky 讨论了模式匹配。

模式匹配是什么?

Bill Venners: Scala 支持 _ 模式匹配 _。这是一种函数式编程技术,过去尚未在主流语言中出现过。你能解释一下它是什么,以及我们为什么需要它?

Martin Odersky: 模式匹配并不很新,(上世纪)七十年代中期就已经有语言采用。据我所知,第一种语言是 ML,但可能也有更早的语言支持。它在许多函数式语言中都算是标准功能,包括 ML、Caml、Erlang、以及 Haskell。

那么什么是模式匹配呢?它可以让你给一个值匹配多种情况,有点像 Java 中的 switch 语句。但它不仅可以像 switch 语句一样用来匹配数字,还可以匹配对象的内在构建形式。

比如,Scala 中的 List 存在两种情况:要么是空 List,写做 Nil;要么由一个 _head_ 元素紧接着另一 List _tail_ 组成。有了模式匹配,你可以询问:给定的 List 是空 List 吗?只要编写 case Nil、箭头 (=>) 以及后续表达式即可:

case Nil => // 后续表达式你还可以询问:它是非空 List 吗?只要编写 case x :: xs、箭头、以及后续表达式即可:

case x :: xs => // 后续表达式双冒号(::)表示 _cons_ 操作符;x 表示 List 的首元素,xs 表示剩余部分。于是,模式匹配会首先区分 List 是否为空。而如果 List_ 非 _ 空,它会把 List 的首元素命名为 x 然后把 List 剩余部分命名为 xs。接下来,这些变量可以被箭头右侧表达式所用。(参见示例 1)

示例 1:match 表达式

复制代码
list match {
case Nil => "was an empty list"
case x :: xs => "head was " + x + ", tail was " + xs
}

如果 list 不为空,将匹配到第二种情况,List 首元素将赋值给 x,而列表剩余部分赋值给 xs。接下来,这些变量将被箭头符号右侧的字符串连接表达式所用。例如,如果 list 内容是 List(“hello”, “world”),那么匹配表达式的结果将是字符串"head was hello, tail was List(world)"。

上例的模式非常简单。但实际上模式还支持嵌套,类似表达式的嵌套,能让你编写层数很深的模式。总的来说,亮点在于,模式和表达式看起来很像。模式本质上和表达式属于完全一类东西,看上去就像构造表达式一样,可以用来构造复杂树状对象,但却不需要编写 new。事实上,在 Scala 中,该对象构造时一样不需要 new。然后你可以在某些位置填上占位变量,对应树对象中实际存在的值。(参见示例 2)

示例 2:嵌套模式的 match 表达式

复制代码
object match {
case Address(Name(first, last), street, city, state, zip) => println(last + ", " + zip)
case _ => println("not an address") // 默认情况
}

在第一种情况下,模式 Name(first, last) 嵌在模式 Address(…) 中。last 放在了 Name 构造函数内,可以“提取”出值,因而,可供箭头右边的表达式使用。

模式匹配的目的

那么,为什么你需要模式匹配?我们每个人都有复杂的数据。如果我们坚持严格的面向对象的风格,那么我们并不希望直接访问数据内部的树状结构。相反,我们希望调用方法,然后在方法中访问。如果我们能够这样做,那么我们就再也不需要模式匹配了,因为这些方法已经提供了我们需要的功能。但很多情况下,对象并不提供我们需要的方法,而且我们无法(或者不愿)向这些对象添加方法。

例如 XML。如果给你一棵 XML 树,那么树就只是单纯的数据。要么是节点,要么是节点的序列。XML 是一种非常通用的数据表现形式。例如,DOM 本质上只是节点的数组,其中每个节点的类型都未知。现在我们设想一下,如果把 XML 树转换到某种更强的框架中,可以给你一个列表,容纳各种不同类型的对象。组成列表的元素可能包括诸如电话号码、备忘录或地址等。如果你想以静态类型的方式获取所有这些东西,就会遇上一个问题:你不知道每个元素的类型。在传统面向对象的编程语言中,唯一可行方式是,编写一大堆 instanceof 检测,一一测试每个元素是 PhoneNumber 实例、Memo 实例,还是其他实例。一旦这些 instanceof 语句之一检测成功,你还需要进行类型转换。上述做法相当丑陋和笨拙,有了模式匹配就能避免了。模式匹配能以更安全、更自然的方式完成相同功能。

从本质上讲,当你从外部取得具有结构的对象图时,模式匹配就必不可少。你会在若干情况下遇到这种现象,XML 是其中之一。各种从文本解析而来的数据,都属于这一类。例如,有一种典型情况下模式匹配必不可少,即,处理编译器中的抽象语法树的情况。如果你要对表达式进行化简操作,表达式会被表示为树,你需要通过模式匹配对这些树进行提取操作。类似那样的情况还有许多。遇到这些情况时,模式匹配真的必不可少。

反向构造对象

Bill Venners: 你说模式像表达式,而且就像某种反向的表达式。正向的表达式向结果中插入值,反向的表达式却是给定结果,一旦匹配成功,就能反过来从结果中抽取出一大堆值。

Martin Odersky: 对。它还真就是反向的构造器。我可以通过嵌套构造器来构造对象,在构造时提供一些参数。比方说,我有一个方法,给它一些参数,就能从这些参数构造出复杂的对象结构。模式匹配正好相反——给定一个复杂数据结构,模式匹配就能抽取出先前用来构造该结构时所用的参数。

可扩展性的两个方向

Bill Venners: 听起来你好像在谈及一种面向对象的解决方案,解决的问题是:如何在现有对象中新增涉及内部数据的行为。理想情况下,你会把方法添加到子类型中,比如 Memo、Address 以及任何其他节点类型。你会在它们公共超类上调用这些方法,这些方法会通过动态绑定找到某个具体类。好比说,“我是 Memo,我干 Memo 该干的事。”但你说的问题是,往往你没办法轻易添加方法。

Martin Odersky: 是的,就是这样。关键问题是,你在什么时候添加方法?这个问题多半是在质问可扩展性。举个典型的面向对象例子:图形用户界面。你有很多不同的组件,都能做相同的事情。它们可以显示、可以隐藏、可以重绘……诸如此类。你与这些组件交互的协议是固定的,但你要打交道的组件数量却是无限的。用户时时刻刻都在发明新的图形用户界面组件。在这种情况下,面向对象的方法是正解。而且是唯一正解。事实上,有史以来第一种面向对象语言,理当是用于仿真领域的。该语言是 Simula-67 ,其主要用途就很类似图形用户界面领域。而第二种面向对象语言, Smalltalk ,则是在史上第一套实用图形用户界面开发的同时发明的。所以,这种语言真正回答了以下问题:怎样以可扩展的方式编写图形用户界面?

但这仅仅是可扩展性的观念之一。如果涉及一组相对固定的结构,则需要另一种观念。虽然你不想改变结构,但是你想要对这一组结构做的操作,却有无尽的可能。你会一直想要添加新的操作。典型的例子是编译器。编译器处理语法树,而语法树表示你写的程序。只要你不修改语言规范,语法树结构就会保持不变。这颗树一直都一个样。然而,编译器想要对语法树做的事情则天天都会变。明天你可能就会设想要新增一个优化阶段,需要遍历语法树。所以,你需要某种方式,把操作定义在树以外,因为,不然的话,每当你要向编译器添加新的优化阶段等行为时,你就必须为所有树节点类添加新方法。这显然非常昂贵、非常麻烦。

所以,完成工作的正确工具是什么?确实取决于你希望扩展的方向。如果你想要扩展新的数据,那么就该选择经典的面向对象途径,以及虚方法。如果你想保持数据类型固定,而只扩展新的操作,那么模式匹配更合适些。实际上面向对象编程中有一种设计模式(注意“设计模式”与“模式匹配”中的“模式”二字含义不同)叫做访问者模式。我们利用模式匹配能做的事,也可以用访问者模式来表达。但访问者模式利用了面向对象的虚方法分发机制。然而实践中,访问者模式非常笨重。很多用模式匹配很容易做到的事情,用访问者模式做不到。最终会导致访问者实现代码非常厚重。而且,最后我们发现,基于现代的虚拟机技术,访问者模式要比模式匹配性能更低。鉴于这两个原因,我认为模式匹配的确有用武之地。

阅读英文原文 The Point of Pattern Matching in Scala


感谢魏星对本文的策划和审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。

2016-01-20 16:253408

评论

发布
暂无评论
发现更多内容

MySQL常用函数介绍

Simon

MySQL mysql常用函数

大数据平台架构设计探究

vivo互联网技术

大数据 架构设计 数据平台

Linux-技术专题-buffer/cache理解

洛神灬殇

必看的数据库使用规范

Simon

MySQL 技术规范

2020中国RPA指数测评报告|T研究

人称T客

互联网公司建网站时最应该注意什么?

姜奋斗

互联网 网络安全 网站 网站搭建 互联网公司

天地玄黄,宇宙洪荒

zhoo299

随笔杂谈

拼多多员工小便池拉屎,网易智能马桶屏蔽信号,360、搜狐厕所被监控,互联网公司厕所那些事!

程序员生活志

互联网 职场

200 行代码就能骗人的首个聊天机器人

程序员生活志

编程 机器人

Java基础知识篇(2020最新版)准备放进收藏夹吃灰的勿进

简爱W

Java

从零开始的深度学习实用教程 | PyTorch官方推荐

迈微AI研发社

人工智能 学习 算法 教程 PyTorch

给路灯按上“电话卡”,从此不仅只照明还给管理员“打电话”

华为云开发者联盟

人工智能 物联网 物联网化 华为云 路灯

机器学习算法之——隐马尔可夫模型原理详解及Python实现

迈微AI研发社

Python 学习 算法 隐马尔可夫模型 HMM

机器学习 | 卷积神经网络详解(二)——自己手写一个卷积神经网络

迈微AI研发社

Python 神经网络 学习 卷积神经网络 CNN

浅谈备受开发者好评的.NET core敏捷开发工具,讲讲LEARUN工作流引擎

Philips

区块链最激动人心的未来是什么

CECBC

大数据 区块链技术

时间戳,这样用就对了

Simon

MySQL timestamp

机器学习算法之——卷积神经网络(CNN)原理讲解

迈微AI研发社

神经网络 学习 算法 CNN

机器学习算法之——逻辑回归(Logistic Regression)原理详解及Python实现

迈微AI研发社

学习 算法 逻辑回归 正则化 梯度下降

一个域名值百万, 现在不注册,未来价更高

华为云开发者联盟

备案 商标 DNS 域名配置 SSL证书

InnoDB 事务加锁分析

vivo互联网技术

MySQL 数据库 innodb

CDN百科11 | 担心纸质病例被洪水淹没?ECS+CDN+OSS如何助力医疗上云

阿里云Edge Plus

CDN 存储 云服务器

带你认识MySQL sys schema

Simon

MySQL

三分钟看懂Python和Java的区别

程序员生活志

Java Python

vivo web service:亿万级规模web服务引擎架构

vivo互联网技术

架构 Web 浏览器

前端科普系列(1):前端简史

vivo互联网技术

大前端 Web

高效程序员的45个习惯:敏捷开发修炼之道(4)

石云升

读书笔记 敏捷开发

获奖公布丨程序员的七夕骚话该怎么讲?留下你爱的表白~

InfoQ写作社区官方

写作平台 话题讨论 七夕 热门活动

【杭州】阿里巴巴搜索推荐事业部开发岗位招聘

iSausage

Java 阿里巴巴 推荐 搜索

牧羊少年奇幻之旅

W

读书笔记 感悟

我一个普通程序员,光靠GitHub打赏就年入70万,要不你也试试

程序员生活志

Scala模式匹配的亮点——Martin Odersky访谈(四)_Java_Bill Venners_InfoQ精选文章