Scala的模式匹配和条件类

树是在程序中常用的一个数据结构。例如编译器和解析器常常吧程序表示为树;XML文档结构也是树状的;还有一些集合是基于树的,例如红黑树。
接下来我们将通过一个计算器程序来研究树在Scala中是如何表示和操纵的。这个程序的目标是处理一些由整数常量、变量和加号组成的简单的算数表达式,例如1 + 2 和 (x + x ) + (7 + y )。
我们首先要决定如何表示这些表达式。最自然的方法就是树了,树的节点表示操作符(在这里只有加法),而树的叶节点表示值(这里表示常数和变量)。 在Java中,这样的树可以表示为一个超类的树的集合,节点由不同子类的实例表示。而在函数式语言中,我们可以使用代数类型(algebraic data-type)来达到同样的目的。Scala提供了一种介于两者之间的叫做条件类(Case Classes)的东西。
abstract class Tree case class Sum(l: Tree, r: Tree) extends Tree case class Var(n: String) extends Tree case class Const(v: Int) extends Tree
我们实际上定义了三个条件类 Sum ,Var 和 Const 。这些类和普通类有若干不同:
1. 实例化时可以省略new关键字(例如你可以使用 Const(5)而不必使用 new Const(5) )
2. 参数的getter函数自动定义(例如你可以通过c.v来访问类Const的实例c在实例化时获取的参数v)
3. 拥有默认的预定义equals和hashCode实现,这些实现可以按照值区别类实例是否相等,而不是通过用。
4. 拥有默认的toString实现。这些实现返回值的代码实现(例如表达式x+1可以被表达成Sum(Var(x),Const(1)))
5. 条件类的实例可以通过模式匹配进行分析,我们接下来就要讲这个特性。
现在我们已经定义了表示我们算数表达式的数据类型,于是我们可以开始给他们定义对应的操作。我们将会首先编写一个在上下文中下计算表达式的函数。这里的上下文指的是变量与值的绑定关系。例如表达式x+1在x=5上下文中应该得出结果6。
这样一来我们需要找到一个表示这种绑定关系的方法。当然我们可以使用某种类似hash-table的数据结构,不过我们也可以直接使用函数!一个上下文无非就是一个吧名称映射到值的函数。例如上面给出的{x → 5}的这个映射我们就可以在Scala中表示为:
{ case "x" => 5 }
这个定义了一个函数:当参数等于字符串"x" 时返回整数5,否则抛出异常。
在编写求值函数之前我们,我们需要给我们的上下文起个名字,以便在后面的代码里面引用。理所应当的我们使用了类型String=>Int,但 是如果我们给这个类型起个名字,将会让程序更加简单易读,而且更加容易维护。在scala中,这件事情可以通过以下代码完成:
type Environment = String => Int
从现在开始,类型Environment就当作String到Int的函数类型名来使用了。
现在我们可以开始定义求值函数了。从概念上来说,这是很简单的一个过程:两个表达式之和等于两个表达式分别求值后再求和;变量的值可以从上下文中提取;常量的值就是他本身。在Scala中表达这个没有什么难度:
def eval(t: Tree, env: Environment): Int = t match { case Sum(l, r) => eval(l, env) + eval(r, env) case Var(n) => env(n) case Const(v) => v }
求值函数通过对树t进行模式匹配来完成工作。直观的来看,上述代码的思路是十分清晰的:
1. 第一个模式检查传入的树的根节点是否是一个Sum,如果是,它将会吧树的左边子树赋值给l,右边的子树赋值给r,然后按照箭头后面的代码进行处理;这里的代码可以(并且的确)使用了在左边匹配时所绑定的变量,比如这里的l和r。
2. 如果第一个检查没有成功,表明传入的树不是Sum,程序继续检查他是不是一个Var;如果是,则吧变量名赋给n然后继续右边的操作。
3. 如果第二个检查也失败了,表示t既不是Sum也不是Var,程序检查他是不是Const。如果是着赋值变量并且继续。
4. 最后,如果所有检查都失败了。就抛出一个异常表示模式匹配失败。这只有在Tree的其他之类被定义时才可能发生。
我们可以看出模式匹配的基本思想就是试图对一个值进行多种模式的匹配,并且在匹配的同时将匹配值拆分成若干子项,最后对匹配值与其子项执行某些代码。
一个熟练的面向对象的程序员可能想知道为什么我们不吧eval定义为Tree或者其之类的成员函数。我们事实上可以这么做。因为Scala允许条件类象普通类那样定义成员。决定是否使用模式匹配或者成员函数取决于程序员的喜好,不过这个取舍还和可扩展性有重要联系:
1. 当你使用成员函数时,你可以通过继承Tree从而很容易的添加新的节点类型,但是另外一方面,添加新的操作也是很繁杂的工作,因为你不得不修改Tree的所有子类。
2. 当你使用模式匹配是,形势正好逆转过来,添加新的节点类型要求你修改所有的对树使用模式匹配的函数,但是另一方面,添加一个新的操作只需要再添加一个模式匹配函数就可以了。
下面我们来更详细的了解模式匹配,让我们再给表达式定义一个操作:对符号求导数。读者们也许想先记住下面关于此操作的若干规则:
1. 和的导数等于导数的和,
2. 如果符号等以求导的符号,则导数为1,否则为0.
3. 参数的导数永远为0。
上述规则可以直接翻译成Scala代码:
def derive(t: Tree, v: String): Tree = t match { case Sum(l, r) => Sum(derive(l, v), derive(r, v)) case Var(n) if (v == n) => Const(1) case _ => Const(0) }
这个函数使用了两个关于模式匹配的功能,首先case语句可以拥有一个guard子句:一个if条件表达式。除非guard的条件成立,否则该模式不会成功匹配。其次是通配符:_ 。这个模式表示和所有值匹配而不对任何变量赋值。
事实上我们还远没有触及模式匹配的全部精髓。但是我们限于篇幅原因不得不再此停笔了。下面我们看看这个两个函数是如何在一个实例上运行的。为了达到这个目前我们写了一个简单的main函数来对表达式(x + x ) + (7 + y )进行若干操作:首先计算当{x → 5, y → 7}时表达式的值,然后分别对x和y求导。
def main(args: Array[String]) { val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) val env: Environment = { case "x" => 5 case "y" => 7 } println("Expression: " + exp) println("Evaluation with x=5, y=7: " + eval(exp, env)) println("Derivative relative to x:\n " + derive(exp, "x")) println("Derivative relative to y:\n " + derive(exp, "y")) }
执行程序,我们能得到以下输出:
Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y))) Evaluation with x=5, y=7: 24 Derivative relative to x:
Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0))) Derivative relative to y: Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1)))
通过研究程序输出,我们能看到求导的输出可以在被打印之前简化,使用模式匹配定义一个简化函数是挺有意思的(不过也需要一定的技巧)工作。读者可以尝试自己完成这个函数。

更多精彩内容请 关注:http://bbs.superwu.cn

关注超人学院微信二维码:

时间: 2024-10-11 20:12:58

Scala的模式匹配和条件类的相关文章

【Scala】模式匹配和样本类

模式匹配 要理解模式匹配(pattern-matching),先把这两个单词拆开,先理解什么是模式(pattern),这里所的模式是数据结构上的,这个模式用于描述一个结构的组成. 我们很容易联想到"正则表达"里的模式,不错,这个pattern和正则里的pattern相似,不过适用范围更广,可以针对各种类型的数据结构,不像正则表达只是针对字符串.比如正则表达式里 "^A.*" 这个pattern 表示以A开头.后续一个或多个字符组成的字符串:List("A&

Scala学习文档-样本类与模式匹配

样本类:添加了case的类便是样本类.这种修饰符可以让Scala编译器自动为这个类添加一些语法上的便捷设定. //样本类case class //层级包括一个抽象基类Expr和四个子类,每个代表一种表达式 //样本类自动添加与类名一致的工厂方法 abstract class Expr case class Var(name:String) extends Expr//括号内参数不用加val,默认为加val的字段 case class Number(num:Double) extends Expr

Scala之模式匹配(Patterns Matching)

前言 首先,我们要在一开始强调一件很重要的事:Scala的模式匹配发生在但绝不仅限于发生在match case语句块中,这是Scala模式匹配之所以重要且有用的一个关键因素!我们会在文章的后半部分详细地讨论这一点. 模式匹配的种类 在Scala中一共有如下几种类型的模式匹配: 通配符匹配(Wildcard Pattern Matching ) 常量匹配 (Constant Pattern Matching ) 变量匹配(Variable Pattern Matching ) 构造函数匹配(Con

Scala的模式匹配本质是什么? -从Coursera的响应式编程说起

推荐Coursera上的响应式编程课程,这个课程是scala语言的进阶课程. 课程的开始提出了这样一个应用场景:构建Json串,不知道Json的同学随便google一下. 为了做到这些事情,我们定义了下面的一些类 abstract class JSON case class JSeq(elems: List[JSON]) extends JSON case class JObj(bindings: Map[String, JSON]) extends JSON case class JNum(n

好程序员大数据教程Scala系列之样例类_Option_偏函数

好程序员大数据教程Scala系列之样例类_Option_偏函数,在Scala中Option类型样例类用来表示可能存在或也可能不存在的值(Option的子类有Some和None).Some包装了某个值,None表示没有值. object?OptionDemo {??def?main(args: Array[String]) {????val?map = Map("a"?-> 1, "b"?-> 2)????val?v = map.get("b&q

Scala 系列(八)—— 类和对象

一.初识类和对象 Scala 的类与 Java 的类具有非常多的相似性,示例如下: // 1. 在 scala 中,类不需要用 public 声明,所有的类都具有公共的可见性 class Person { // 2. 声明私有变量,用 var 修饰的变量默认拥有 getter/setter 属性 private var age = 0 // 3.如果声明的变量不需要进行初始赋值,此时 Scala 就无法进行类型推断,所以需要显式指明类型 private var name: String = _

【转】Scala学习——模式匹配和样例类

原文链接 http://nerd-is.in/2013-09/scala-learning-pattern-matching-and-case-classes/ 原文发表于:http://nerd-is.in/2013-09/scala-learning-pattern-matching-and-case-classes/ Scala强大的模式匹配机制,可以应用在switch语句.类型检查以及“析构”等场合. 样例类对模式匹配进行了优化. 更好的switch 1 2 3 4 5 6 7 8 va

scala模式匹配及样本类

样本类 1.带有case关键字的类被称为样本类: 例如:abstract class Expr case class Var(name: String) extends Expr case class Number(num: Double) extends Expr case class UnOp(operator:String,arg: Expr) extends Expr case class BinOp(operatot: String,left:Expr,right:Expr) exte

快学Scala(14)--模式匹配和样例类

更好的switch def main(args: Array[String]): Unit = { var sign: Int = 0 val ch: Char = '+' val color = Color.BLACK sign = ch match { case '+' => 1 case '-' => -1 case _ => 0 } color match { case Color.RED => ; case Color.BLACK => ; case _ =>