《Scala by Example》第7章 样本类和模式匹配


  现在,我们想要写一个对于数学计算表达式的解释器。为了保持内容简单,我们规定这里只有数字和“+”运算。这样的表达式可以表示成一个类继承关系。有一个抽象基类Expr作为根类,和两个子类:Number和Sum。则表达式 1 + (3 + 7) 可以表示成:

new Sum(new Number(1) , new Sum(new Number(3) , new Number(7)))

现在,一个像这样的表达式要知道传入的是什么类型(Sum或者Number)。下面的实现提供了所有必要的方法。

类:Expr

abstract class Expr {
  def isNumber : Boolean 
  def isSum : Boolean 
  def numValue : Int
  def leftOp : Expr
  def rightOp : Expr

}

类Number:

class Number(n : Int) extends Expr {

  def isNumber(): Boolean = true 

  def isSum(): Boolean = false

  def numValue(): Int = n 

  def leftOp(): Expr = error("Number.leftOp")

  def rightOp(): Expr = error("Number.rightOp")

}

类Sum

class Sum(e1 : Expr , e2 : Expr) extends Expr {

  def isNumber(): Boolean = false

  def isSum(): Boolean = true

  def numValue(): Int = error("Sum.numValue")

  def leftOp(): Expr = e1

  def rightOp(): Expr = e2

}

有了这些类,可以写出如下的计算函数

def eval( e : Expr) : Int = {
    if ( e.isNumber) e.numValue
    else if ( e.isSum) eval(e.leftOp) + eval(e.rightOp)
    else error("unrecognized expression kind")
  }

但是这样的定义很枯燥。更甚者,如果我们想增加一种新的表达式类型就会发生一些问题。例如,如果想增加一个类Prod用来表示products,那么不仅要实现这个新类,而且还要在Expr中加入对这种类型的判断。不得不改变已存在的代码对于一个增长的系统而言,经常会引入问题,包括版本和维护问题等。

  对面向对象语言而言,这样的修改应该是非必须的。

  考虑一下,如果用上面的方式,增加一个类型或者增加一个方法,会改动到很多类文件。我们要考虑的是,首先一个抽象基类的哪个子类要被用到,其次,构造参数是什么。因为这个问题很普通,Scala提供了case class来自动解决这个问题。

7.1  case class 和 case objects

   定义是和普通的Class,object一样的,只是多了一个修饰符case 。如以下定义:

abstract class Expr
case class Number( n :Int) extends Expr
case class Sum(e1 : Expr , e2 : Expr) extends Expr

这样就引入了两个case class 。 定义为case,有以下的特性:

  • Case Class 隐式地就有了一个构造方法,与类名一致。可以直接使用Sum(Sum(Number(1) , Number(2)) , Number(3))
  • 隐式地有了toString , equals 和hashCode的实现。在重写的时候把这个类里所有的成员都用上。(书上说的是把类参数当成val去维护)
  • 可以直接访问构造参数。否则在其他的类中,应该再提供类似于def n : Int 这样的方法来让外界访问这个变量。
  • 样本类允许模式构造指向样本类的构造器(?翻译的可能有问题。回头应该再看看《Scala编程》中的说法,尼玛,没有!)

7.2 模式匹配

  模式匹配是一个从C或Java的switch表达式泛化到类层级。Scala里用match 代替了switch表达式。match表达式里带有很多的case 。 如下是一个用模式匹配来实现的eval方法:

def eval(e : Expr) : Int = e match{
  case Number(n) => n
  case Sum(l , r) => eval(l ) + eval(r)
}

例子中有两个case。每个模式都与一个表达式相关联。通常,“模式”由以下几个部分构成:

  • 样本类构造器,如Number,Sum,参数可以也是模式;
  • 模式参数,如e1 , e2
  • 通配符:_
  • 字面量,如 1, true , "abc"
  • 常量,如MAXINT , EmptySet

模式参数通常以小写字母开头,以便与常量标识符区分开。常量要以大写开头。每个变量名可能只在模式里出现一次。

模式匹配意义:模式匹配表达式:

 e match {case p1 => e1 ... case pn => en}

根据顺序来进行匹配。如果没有匹配的,会抛出MatchError异常

模式匹配和方法: 在之前的例子中,我们把模式匹配定义在类层级外。当然也可以把它定义在类层级内。例如,可以把eval定义在基类Expr中,同样也用到模式匹配:

abstract class Expr{
  def eval : Int = this match {
    case Number(n) => n 
    case Sum(e1 , e2) => e1.eval + e2.eval
  }
}

模式匹配匿名函数:到现在为止,case表达式总是跟match操作一起出现。但也可单独使用case表达式。代码块如下:

{case P1 => E1 ... case Pn => En}

只能被自己所见,并进行匹配。换言之,可以被看成为以下匿名函数的缩写:

(x => x match{case P1 => E1 ... case Pn => En})

x是一个不被表达式其他地方使用的新变量。

 

 

 

 

 

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM