一些刚从java转到scala的同学在开发的过程中犹如深陷沼泽,因为很多的概念或风格不确定,scala里有很多的坑,模式匹配也算一个。我整理了一下自己所理解的概念,以及一些例子。这个系列最好有些scala的基本经验,或者接触过一些其他函数式语言。

要理解模式匹配(pattern-matching),先把这两个单词拆开,先理解什么是模式(pattern),这里所的模式并不是设计模式里的模式。
而是数据结构上的,这个模式用于描述一个结构的组成。

我们很容易联想到“正则表达”里的模式,不错,这个pattern和正则里的pattern相似,不过适用范围更广,可以针对各种类型的数据结构,不像正则表达只是针对字符串。

比如正则表达式里 “^A.*” 这个pattern 表示以A开头、后续一个或多个字符组成的字符串;
List(“A”, _,  _*) 也是个pattern,表示第一个元素是”A”,后续一个或多个元素的List。

狭义的看,模式可以当作对某个类型,其内部数据在结构上抽象出来的表达式。如上面的List(“A”, _, _*) 就是一种List结构的pattern。
模式匹配(pattern-matching)则是匹配变量是否符合这种pattern。比如List(“A”,”B”)和List(“A”,”X”,”Y”) 就符合上面的pattern,而List(“X”)则不符合。

直观的看几个例子:
// 匹配一个数组,它由三个元素组成,第一个元素为1,第二个元素为2,第三个元素为3
scala> Array(1,2,3) match { case Array(1,2,3) => println(“ok”)}
ok
// 匹配一个数组,它至少由一个元素组成,第一个元素为1
scala> Array(1,2,3) match { case Array(1,_*) => println(“ok”)}
ok
// 匹配一个List,它由三个元素组成,第一个元素为“A”,第二个元素任意类型,第三个元素为”C”
scala> List(“A”,”B”,”C”) match{ case List(“A”,_,”C”) => println(“ok”) }
ok

例子中的:Array(1,2,3) ,List(“A”,_,”C”) 等都是模式,表示由指定元素组成的某种类型。
当然模式也不仅仅是表示某种结构的,还可以是常量,或类型,如:
scala> val a = 100
a: Int = 100

// 常量模式,如果a与100相等则匹配成功
scala> a match { case 100 => println(“ok”) }
ok

// 类型模式,如果a是Int类型就匹配成功
scala> a match { case _:Int => println(“ok”) }
ok

在 scala里对pattern有明确的定义,在形式上有以下几种pattern:

1) 常量模式(constant patterns) 包含常量变量和常量字面量

scala> val site = “alibaba.com”
scala> site match { case “alibaba.com” => println(“ok”) }

scala> val ALIBABA=”alibaba.com”
scala> def foo(s:String) { s match { case ALIBABA => println(“ok”) } } //注意这里常量必须以大写字母开头

常量模式和普通的 if 比较两个对象是否相等(equals) 没有区别,并没有感觉到什么威力

2) 变量模式(variable patterns)
确切的说单纯的变量模式没有匹配判断的过程,只是把传入的对象给起了一个新的变量名。
scala> site match { case whateverName => println(whateverName) }
上面把要匹配的 site对象用 whateverName 变量名代替,所以它总会匹配成功。
不过这里有个约定,对于变量,要求必须是以小写字母开头,否则会把它对待成一个常量变量,
比如上面的whateverName 如果写成WhateverName 就会去找这个WhateverName的变量,如果找到则比较相等性,找不到则出错。

变量模式通常不会单独使用,而是在多种模式组合时使用,比如 List(1,2) match{ case List(x,2) => println(x) }
里面的x就是对匹配到的第一个元素用变量x标记。

3) 通配符模式(wildcard patterns)
通配符用下划线表示:”_” ,可以理解成一个特殊的变量或占位符。
单纯的通配符模式通常在模式匹配的最后一行出现,case _ =>  它可以匹配任何对象,用于处理所有其它匹配不成功的情况。

通配符模式也常和其他模式组合使用:
scala> List(1,2,3) match{ case List(_,_,3) => println(“ok”) }
上面的 List(_,_,3) 里用了2个通配符表示第一个和第二个元素,这2个元素可以是任意类型
通配符通常用于代表所不关心的部分,它不像变量模式可以后续的逻辑中使用这个变量。

4) 构造器模式(constructor patterns)
这个是真正能体现模式匹配威力的一个模式!

我们来定义一个二叉树:

scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Node //抽象节点
case class TreeNode(v:String, left:Node, right:Node) extends Node //具体的节点实现,有两个子节点
case class Tree(root:TreeNode)  //Tree,构造参数是根节点
// Exiting paste mode, now interpreting.

这样我们构造一个根节点含有2个子节点的数:
scala>val tree = Tree(TreeNode(“root”,TreeNode(“left”,null,null),TreeNode(“right”,null,null)))

如果我们期望一个树的构成是根节点的左子节点值为”left”,右子节点值为”right”并且右子节点没有子节点
那么可以用下面的方式匹配:
scala> tree.root match { case TreeNode(_,TreeNode(“left”,_,_), TreeNode(“right”,null,null)) => println(“bingo”) }

只要一行代码就可以很清楚的描述,如果用java实现,是不是没这么直观呢?

5) 类型模式(type patterns)
类型模式很简单,就是判断对象是否是某种类型:
scala> “hello” match { case _:String => println(“ok”) }

跟 isInstanceOf 判断类型的效果一样,需要注意的是scala匹配泛型时要注意,
比如
scala> def foo(a:Any) { a match { case a :List[String] => println(“ok”); case _ => } }
如果使用了泛型,它会被擦拭掉,如同java的做法,所以上面的 List[String] 里的String运行时并不能检测
foo(List(“A”))  和 foo(List(2)) 都可以匹配成功。实际上上面的语句编译时就会给出警告,但并不出错。
通常对于泛型直接用通配符替代,上面的写为 case a : List[_] => …

6) 变量绑定模式 (variable binding patterns)
这个和前边的变量模式有什么不同?看一下代码就清楚了:

依然是上面的TreeNode,如果我们希望匹配到左边节点值为”left”就返回这个节点的话:
scala> tree.root match { case TreeNode(_, leftNode@TreeNode(“left”,_,_), _) => leftNode }
用@符号绑定 leftNode变量到匹配到的左节点上,只有匹配成功才会绑定

另外解释一下抽取器模式(extractor patterns),一些资料里也会提到这个模式
抽取器是一种实现模式匹配的技术方式,在表现上,抽取器模式与构造器模式一致,都是 case A(e1,e2) => 这样的形式。
在《Programming in scala》一书中提到 序列模式(sequence patterns),针对所有SeqFactory的子类,它其实就是抽取器模式。
在表达形式上 case List(1,2,3) => … 或 case Array(“a”,”b”) => …  看着与构造器模式一模一样(就是背后实现有所不同)
所以在模式的表现形式上,不适合把它划为一类,非要把 序列模式 与构造器模式区分的话,也是从它们背后的实现上,而不是表现上。

另外《Programming in scala》一书中也单独提到 元组模式(tuple patterns), 元组模式本质上也是一个构造器模式。

了解完模式匹配的感念后,我们后续再看一下scala里是怎么实现模式匹配的

话说模式匹配(1) 什么是模式?相关推荐

  1. 话说模式匹配(5) for表达式中的模式匹配

    在for表达式中 for(x <- collection) { balabala } 直觉上以为 x 就是个用于迭代每一个元素的局部变量. 我们看一些例子: scala> for(i &l ...

  2. C/C++——朴素的模式匹配算法和KMP模式匹配算法

    朴素的模式匹配算法 其实就是一个一个往下匹配,没有任何优化,在好的情况下时间复杂度为O(n+m),在最求的情况下时间复杂度为O((n-m+1)*m). 代码实现: //在主串s中找子串t,若找到返回字 ...

  3. Scala模式匹配和类型系统

    Scala模式匹配和类型系统 1.模式匹配比java中的switch case强大很多,除了值,类型,集合等进行匹配,最常见的Case class进行匹配,Master.scala有大量的模式匹配. ...

  4. scala 模式匹配详解 1

    什么是模式? 一些刚从java转到scala的同学在开发的过程中犹如深陷沼泽,因为很多的概念或风格不确定,scala里有很多的坑,模式匹配也算一个.我整理了一下自己所理解的概念,以及一些例子.这个系列 ...

  5. C# 7.0 新特性3: 模式匹配

    本文参考Roslyn项目Issue:#206,及Docs:#patterns. 1. C# 7.0 新特性1: 基于Tuple的"多"返回值方法 2. C# 7.0 新特性2: 本 ...

  6. string字符串的查找替换、模式匹配

    一.查找字符串的位置 i,j = string.find(str,"要查找的字符串"[,pos[, plain]]); 使用模式匹配查找字符串并返回起始位置(i),结束位置(j); ...

  7. 数据结构笔记(十四)-- 串的模式匹配算法

    串的模式匹配算法 一.普通模式匹配算法 1.算法解析 普通模式匹配算法,其实现过程没有任何技巧,就是简单粗暴地拿一个串同另一个串中的字符一一比对,得到最终结果. 例如,使用普通模式匹配算法判断串 T( ...

  8. php seekdir,perl 模式匹配总结和shell命令调用方法 (zz)

    正则表达式――perl模式匹配 Practiacl Extraction and Report Language 一.模式匹配操作符 1.tr/ / / 替换操作符不支持正则表达式 也不具备双引号替换 ...

  9. 简单模式匹配算法——串的模式匹配

    一.对一个串中的某子串的定位操作称为 串的模式匹配: 二.模式串:待定位的子串 三.基本思想:从主串中的第一个位置起和模式串的第一个字符开始比较 如果相等,则继续比较后续字符: 如果不等,则从主串的第 ...

最新文章

  1. Oracle记录被另一个用户锁住
  2. POJ NOI MATH-7828 最大公约数与最小公倍数
  3. 【译】Jumping into Solidity — The ERC721 Standard (Part 3)
  4. 对比四大企业级linux版本_Linux家族族谱
  5. Python基础学习Day3 数据类型的转换、int、str、bool、字符串的常用方法、for循环...
  6. 频段、信道带宽、EARFCN、频段和EARFCN之间的换算
  7. mappedBy作用
  8. 【信号与系统】(十三)傅里叶变换与频域分析——周期信号的傅里叶级数
  9. 医院私有云架构统一灾备中心建设最佳实践
  10. Vagrant 入门 - 同步目录(synced folders)
  11. python爬取餐饮数据_使用 Python 分析全国所有必胜客餐厅
  12. YDOOK: ANSYS Maxwell 19 教程11:Maxwell 2D 静电场怎样设置激励源
  13. python词频统计之红楼梦_用 Python 分析《红楼梦》,后四十回是曹雪芹所写
  14. 奖金711万!这位“山东宝妈”破解美国运算100万年才可能解开的密码!
  15. vite.config.js配置入门与小记
  16. 数据库update(动态更新)-Oracle
  17. python程序创建词云 中国地图_Python基于wordcloud及jieba实现中国地图词云图
  18. 小白入坑安全测试指南
  19. python输入秒数输出分钟小时_Python函数将秒到分钟,小时,天问题,怎么解决
  20. Widget API 接口实例演示(二) ——Telephony类和PIM类

热门文章

  1. 邮件服务器IMail教程(2)
  2. Linux系统管理_ACL访问控制-Redhat Enterprise 5
  3. 40个视差滚动效果网站的新技术应用
  4. 装好XP,打开SATA开机蓝屏解决方法
  5. 修改Linux系统默认编辑器
  6. 用gcc/g++编译运行C/C++程序
  7. phpstudy apache设置伪静态
  8. cocos2dx 云彩特效
  9. C#-老生常谈的 值类型与引用类型
  10. Sicily/1927. Conflict