Scala模式匹配简述

什么是匹配模式?

模式匹配并不很新,(上世纪)七十年代中期就已经有语言采用。据我所知,第一种语言是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 car :: cdr => "head was " + car + ", tail was " + cdr
}

如果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构造函数内,可以“提取”出值,因而,可供箭头右边的表达式使用。

因为匹配是发生在运行期的,而且JVM中泛型的类型信息会被擦掉(跟Java里范型一样不能匹配)。

case m: Map[String, Int] => ...  // 不行,类型不起作用
case m: Map[_, _] => ...  // 匹配通用的Map,OK

但对于数组来说,类型信息是完好的,所以可以在Array上匹配。

对于嵌套结构,举例就能一目了然。

abstarct class Item
case class Article(description: String, price: Double) extends Item
case class Bundle(description: String, price: Double, items: Item*) extends Item
 
Bundle("Father's day special", 20.0, 
  Article("Scala for the Impatient", 39.95),
  Bundle("Anchor Distillery Sampler", 10.0,
    Article("Old Potrero Straight Rye Whisky", 79.95),
    Article("Junipero Gin", 32.95)
  )
)

模式可以匹配到特定的嵌套:

case Bundle(_, _, Article(descr, _), _*) => ...

上面的代码中descr这个变量被绑定到第一个Article的description。另外还可以使用@来将值绑定到变量:

// art被绑定为第一个Article,rest是剩余的Item序列
case Bundle(_, _, art @ Article(_, _), rest @ _*) => ...

样例类
样例类是种特殊的类,经过优化以用于模式匹配。

abstract class Amount
// 继承了普通类的两个样例类
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
 
// 样例对象
case object Nothing extends Amount

使用:

amt match {
  case Dollar(v) => "$" + v
  case Currency(_, u) => "Oh noes, I got " + u
  case Nothing => ""  // 样例对象没有()
}

在声明样例类时,下面的过程自动发生了:
构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
提供unapply方法使模式匹配可以工作;
生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
除了上述之外,样例类和其他类型完全一样,方法字段等。

密封类
当使用样例类来做模式匹配时,如果要让编译器确保已经列出所有可能的选择,可以将样例类的通用超类声明为sealed。
密封类的所有子类都必须在与该密封类相同的文件中定义。
如果某个类是密封的,那么在编译期所有的子类是可知的,因而可以检查模式语句的完整性。
让所有同一组的样例类都扩展某个密封的类或特质是个好的做法。

模式匹配的目的

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 本章要点 match表达式是一个更好的switch,不会有意外掉入到下一个分支的问题。 如果没有模式能够匹配,会抛...
    胡杨1015阅读 1,140评论 0 1
  • scala学习笔记 第2章 变量和数据类型 基本数据 scala的核心数据为四种 :字面量、值、变量、类型 值使...
    485b1aca799e阅读 2,107评论 0 1
  • 这篇讲义只讲scala的简单使用,目的是使各位新来的同事能够首先看懂程序,因为 scala 有的语法对于之前使用习...
    MrRobot阅读 2,896评论 0 10
  • 从匹配中返回值 Match 对象 成功的匹配总是返回一个 Match 对象, 这个对象通常也被放进 $/ 中, (...
    焉知非鱼阅读 1,775评论 0 1
  • 岳: 妈妈,我不想评三好生了可以吗? m: 为什么呢?只要有机会当然要积极争取啦?三好生是对优秀学生的一个证明。 ...
    manxue阅读 431评论 0 0