Scala的模式匹配学习笔记

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

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相比,似乎没有太大的区别,但是在存在不同的参数组合情况的时候,这个写法的优势就体现出来了:

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官网首页上面的这个例子:

// 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”一个函数即可),而这个增加的函数内部是内聚的,增加这个修改符合开闭原则。因此,二者各有利弊,要看设计和使用场景。
上面的这些模式匹配方式组合起来,可以执行一些复杂的匹配,比如基于构造器:

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

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

甚至可以这样:

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

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

时间: 2024-09-21 03:32:23

Scala的模式匹配学习笔记的相关文章

Perl中的模式匹配学习笔记_perl

一.简介模式指在字符串中寻找的特定序列的字符,由反斜线包含:/def/即模式def.其用法如结合函数split将字符串用某模式分成多个单词:@array = split(/ /, $line); 二.匹配操作符 =~.!~=~检验匹配是否成功:$result = $var =~ /abc/;若在该字符串中找到了该模式,则返回非零值,即true,不匹配则返回0,即false.!~则相反.这两个操作符适于条件控制中,如: 复制代码 代码如下:    if ($question =~ /please/

JavaScript学习笔记整理_用于模式匹配的String方法_基础知识

用于模式匹配的String方法: String支持4种使用正则表达式的方法: seach()用于检索,参数是一个正则表达式,返回第一个与之匹配的子串的位置,找不到则返回-1,如果参数不是正则表达式,则首先会通过RexExp构造函数将它转换成正则表达式,seach()方法不支持全局搜索,它忽略修饰符g: replace()用于检索与替换操作,第一个参数是一个正则表达式,第二个参数是要进行替换的字符串.它对调用该方法的字符串检索,按照模式匹配子串替换成第二个参数,若包含修饰符g则全文匹配.若第一个参

机器学习(三)--- scala学习笔记

 Scala是一门多范式的编程语言,一种类似Java的编程语言,设计初衷是实现可伸缩的语言.并集成面向对象编程和函数式编程的各种特性.    Spark是UC Berkeley AMP lab所开源的类Hadoop MapReduce的通用并行框架,Spark,拥有Hadoop MapReduce所具有的优点:但不同于MapReduce的是Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法. 一.sca

Akka学习笔记(一):创建Hello World工程

Akka学习笔记(一):创建Hello World工程 创建工程 使用IDEA,创建SBT工程,在build.sbt中添加akka依赖: name := "My Project" version := "1.0" scalaVersion := "2.10.4" resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/relea

Awk学习笔记

Awk学习笔记 整理:Jims of 肥肥世家 <jims.yang@gmail.com > Copyright 2004 本文遵从GPL协议,欢迎转载.修改.散布. 第一次发布时间:2004年8月6日 博主笔:本人尊重作者的版权 1. awk简介 awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理.数据可以来自标准输入.一个或多个文件,或其它命令的输出.它支持用户自定义函数和 动态正则表达式等先进功能,是linux/unix下的一个强大编程工具.它在命令行中使用,但更多

Bash学习笔记

                                                                          第1 页        共28页 Bash shell学习笔记 .........................................................................................................................2 1. 引言................

Akka学习笔记(七):配置

Akka学习笔记(七):配置 使用Akka可以不用任何配置,Akka提供了明智的默认配置.为了适应特别的运行环境,修改默认行为,你可能需要修改: log level and logger backend enable remoting 消息系列化 路由设置 调度器调优 Akka使用Typesafe Config Library,纯java实现的配置库.之前博客有介绍过here 从哪里读取配置 Akka的所有配置信息装在 ActorSystem的实例中, 或者换个说法, 从外界看来, ActorS

Akka学习笔记(五):Akka与Java的内存模型

Akka学习笔记(五):Akka与Java的内存模型 Akka简化了编写并发软件的过程,本文主要讨论Akka如何在并发应用中访问共享内存. Java内存模型 Java5之前的JMM是相当混乱的.多线程访问共享内存很有可能会得奇怪的结果,如: 可见性问题,无法及时看到其他线程写入的值 指令乱序,观测到其他线程不可能的行为 从Java 5的JSR 133的实现,很多问题就解决了.JMM是基于一组"happens-before"关联规则,限制了访问内存的行为必须在另一个内存访问行为之前发生.

[收藏]AWK学习笔记

Table of Contents 1. awk简介 2. awk命令格式和选项 2.1. awk的语法有两种形式 2.2. 命令选项 3. 模式和操作 3.1. 模式 3.2. 操作 4. awk的环境变量 5. awk运算符 6. 记录和域 6.1. 记录 6.2. 域 6.3. 域分隔符 7. gawk专用正则表达式元字符 8. POSIX字符集 9. 匹配操作符(~) 10. 比较表达式 11. 范围模板 12. 一个验证passwd文件有效性的例子 13. 几个实例 14. awk编程