第9章——模式匹配和正则表达式

模式匹配(pattern matching)在 Scala 被广泛使用的特性中排在第二位,仅次于函数值和 闭包。Scala 对于模式匹配的出色支持意味着,在并发编程中在处理 Actor 接收到的消息时, 将会大量地使用它。在本章中,我们将学到 Scala 的模式匹配的机制、case 类和提取器,以 及如何创建和使用正则表达式。

9.1 模式匹配综述

PatternMatching/MatchLiterals.scala

def activity(day: String): Unit = {
  day match {
    case "Sunday"   ⇒ print("Eat, sleep, repeat... ")
    case "Saturday" ⇒ print("Hang out with friends... ")
    case "Monday"   ⇒ print("...code for fun...")
    case "Friday"   ⇒ print("...read a good book...")
  }
}
List("Monday", "Sunday", "Saturday").foreach { activity }
Full source at GitHub

运行结果

...code for fun...Eat, sleep, repeat... Hang out with friends...
Full source at GitHub

PatternMatching/Wildcard.scala

object DayOfWeek extends Enumeration {
  val SUNDAY: DayOfWeek.Value = Value("Sunday")
  val MONDAY: DayOfWeek.Value = Value("Monday")
  val TUESDAY: DayOfWeek.Value = Value("Tuesday")
  val WEDNESDAY: DayOfWeek.Value = Value("Wednesday")
  val THURSDAY: DayOfWeek.Value = Value("Thursday")
  val FRIDAY: DayOfWeek.Value = Value("Friday")
  val SATURDAY: DayOfWeek.Value = Value("Saturday")
}

def activity(day: DayOfWeek.Value): Unit = {
  day match {
    case DayOfWeek.SUNDAY   ⇒ println("Eat, sleep, repeat...")
    case DayOfWeek.SATURDAY ⇒ println("Hang out with friends")
    case _                  ⇒ println("...code for fun...")
  }
}

activity(DayOfWeek.SATURDAY)
activity(DayOfWeek.MONDAY)
Full source at GitHub

运行结果

Hang out with friends
...code for fun...
Full source at GitHub

PatternMatching/MatchTuples.scala

def processCoordinates(input: Any): Unit = {
  input match {
    case (lat, long) ⇒ printf("Processing (%d, %d)...", lat, long)
    case "done"      ⇒ println("done")
    case _           ⇒ println("invalid input")
  }
}

processCoordinates((39, -104))
processCoordinates("done")
Full source at GitHub

运行结果

Processing (39, -104)...done
Full source at GitHub

PatternMatching/MatchList.scala

def processItems(items: List[String]): Unit = {
  items match {
    case List("apple", "ibm")         ⇒ println("Apples and IBMs")
    case List("red", "blue", "white") ⇒ println("Stars and Stripes...")
    case List("red", "blue", _*)      ⇒ println("colors red, blue,... ")
    case List("apple", "orange", otherFruits @ _*) ⇒
      println("apples, oranges, and " + otherFruits)
  }
}

processItems(List("apple", "ibm"))
processItems(List("red", "blue", "green"))
processItems(List("red", "blue", "white"))
processItems(List("apple", "orange", "grapes", "dates"))
Full source at GitHub

运行结果

Apples and IBMs
colors red, blue,... 
Stars and Stripes...
apples, oranges, and List(grapes, dates)
Full source at GitHub

PatternMatching/MatchTypes.scala

def process(input: Any): Unit = {
  input match {
    case (_: Int, _: Int)          ⇒ print("Processing (int, int)... ")
    case (_: Double, _: Double)    ⇒ print("Processing (double, double)... ")
    case msg: Int if msg > 1000000 ⇒ println("Processing int > 1000000")
    case _: Int                    ⇒ print("Processing int... ")
    case _: String                 ⇒ println("Processing string... ")
    case _                         ⇒ printf(s"Can't handle $input... ")
  }
}

process((34.2, -159.3))
process(0)
process(1000001)
process(2.2)
Full source at GitHub

运行结果

Processing (double, double)... Processing int... Processing int > 1000000
Can't handle 2.2...
Full source at GitHub

9.2 case 表达式中的模式变量和常量

PatternMatching/MatchWithField.scala

class Sample {
  val max = 100

  def process(input: Int): Unit = {
    input match {
      case max ⇒ println(s"You matched max $max")
    }
  }
}

val sample = new Sample
try {
  sample.process(0)
} catch {
  case ex: Throwable ⇒ println(ex)
}
sample.process(100)
Full source at GitHub

运行结果

You matched max 0
You matched max 100
Full source at GitHub

PatternMatching/MatchWithField1.scala

case this.max ⇒ println(s"You matched max $max")
Full source at GitHub

运行结果

scala.MatchError: 0 (of class java.lang.Integer)
You matched max 100
Full source at GitHub

PatternMatching/MatchWithField2.scala

case `max` ⇒ println(s"You matched max $max")
Full source at GitHub

运行结果

scala.MatchError: 0 (of class java.lang.Integer)
You matched max 100
Full source at GitHub

PatternMatching/MatchWithValsOK.scala

class Sample {
  val MAX = 100

  def process(input: Int): Unit = {
    input match {
      case MAX ⇒ println("You matched max")
    }
  }
}

val sample = new Sample
try {
  sample.process(0)
} catch {
  case ex: Throwable ⇒ println(ex)
}
sample.process(100)
Full source at GitHub

运行结果

scala.MatchError: 0 (of class java.lang.Integer)
You matched max
Full source at GitHub

9.3 使用 case 类进行模式匹配

PatternMatching/TradeStock.scala

trait Trade
case class Sell(stockSymbol: String, quantity: Int) extends Trade
case class Buy(stockSymbol: String, quantity: Int) extends Trade
case class Hedge(stockSymbol: String, quantity: Int) extends Trade
Full source at GitHub

PatternMatching/TradeStock.scala

object TradeProcessor {
  def processTransaction(request: Trade): Unit = {
    request match {
      case Sell(stock, 1000) ⇒ println(s"Selling 1000-units of $stock")
      case Sell(stock, quantity) ⇒
        println(s"Selling $quantity units of $stock")
      case Buy(stock, quantity) if quantity > 2000 ⇒
        println(s"Buying $quantity (large) units of $stock")
      case Buy(stock, quantity) ⇒
        println(s"Buying $quantity units of $stock")
    }
  }
}
Full source at GitHub

PatternMatching/TradeStock.scala

TradeProcessor.processTransaction(Sell("GOOG", 500))
TradeProcessor.processTransaction(Buy("GOOG", 700))
TradeProcessor.processTransaction(Sell("GOOG", 1000))
TradeProcessor.processTransaction(Buy("GOOG", 3000))
Full source at GitHub

运行结果

Selling 500 units of GOOG
Buying 700 units of GOOG
Selling 1000-units of GOOG
Buying 3000 (large) units of GOOG
Full source at GitHub

PatternMatching/ThingsAcceptor.scala

case class Apple()
case class Orange()
case class Book()

object ThingsAcceptor {
  def acceptStuff(thing: Any): Unit = {
    thing match {
      case Apple()  ⇒ println("Thanks for the Apple")
      case Orange() ⇒ println("Thanks for the Orange")
      case Book()   ⇒ println("Thanks for the Book")
      case _        ⇒ println(s"Excuse me, why did you send me $thing")
    }
  }
}
Full source at GitHub

PatternMatching/ThingsAcceptor.scala

ThingsAcceptor.acceptStuff(Apple())
ThingsAcceptor.acceptStuff(Book())
ThingsAcceptor.acceptStuff(Apple)
Full source at GitHub

运行结果

Thanks for the Apple
Thanks for the Book
Excuse me, why did you send me Apple
Full source at GitHub

PatternMatching/ThingsAcceptor2.scala

abstract class Thing
case class Apple() extends Thing

object ThingsAcceptor {
  def acceptStuff(thing: Thing) {
    thing match {
      //...
      case _ ⇒
    }
  }
}

ThingsAcceptor.acceptStuff(Apple) //error: type mismatch;
Full source at GitHub

9.4 提取器和正则表达式

PatternMatching/Extractor1.scala

StockService process "GOOG"
StockService process "IBM"
StockService process "ERR"
Full source at GitHub

PatternMatching/Extractor1.scala

object StockService {
  def process(input: String): Unit = {
    input match {
      case Symbol() ⇒ println(s"Look up price for valid symbol $input")
      case _        ⇒ println(s"Invalid input $input")
    }
  }
}
Full source at GitHub

PatternMatching/Extractor1.scala

object Symbol {
  def unapply(symbol: String): Boolean = {
    // you'd look up a database... here only GOOG and IBM are recognized
    symbol == "GOOG" || symbol == "IBM"
  }
}
Full source at GitHub

运行结果

Look up price for valid symbol GOOG
Look up price for valid symbol IBM
Invalid input ERR
Full source at GitHub

PatternMatching/Extractor.scala

object StockService {
  def process(input: String): Unit = {
    input match {
      case Symbol() ⇒ println(s"Look up price for valid symbol $input")
      case ReceiveStockPrice(symbol, price) ⇒
        println(s"Received price $$$price for symbol $symbol")
      case _ ⇒ println(s"Invalid input $input")
    }
  }
}
Full source at GitHub

PatternMatching/Extractor.scala

object ReceiveStockPrice {
  def unapply(input: String): Option[(String, Double)] = {
    try {
      if (input contains ":") {
        val splitQuote = input split ":"
        Some((splitQuote(0), splitQuote(1).toDouble))
      } else {
        None
      }
    } catch {
      case _: NumberFormatException ⇒ None
    }
  }
}
Full source at GitHub

PatternMatching/Extractor.scala

StockService process "GOOG"
StockService process "GOOG:310.84"
StockService process "GOOG:BUY"
StockService process "ERR:12.21"
Full source at GitHub

运行结果

Look up price for valid symbol GOOG
Received price $310.84 for symbol GOOG
Invalid input GOOG:BUY
Received price $12.21 for symbol ERR
Full source at GitHub

PatternMatching/Extractor2.scala

case ReceiveStockPrice(symbol @ Symbol(), price) ⇒
  println(s"Received price $$$price for symbol $symbol")
Full source at GitHub

运行结果

Look up price for valid symbol GOOG
Received price $310.84 for symbol GOOG
Invalid input GOOG:BUY
Invalid input ERR:12.21
Full source at GitHub

PatternMatching/RegularExpr.scala

val pattern = "(S|s)cala".r
val str = "Scala is scalable and cool"
println(pattern findFirstIn str)
Full source at GitHub

PatternMatching/RegularExpr.scala

println((pattern findAllIn str).mkString(", "))
Full source at GitHub

PatternMatching/RegularExpr.scala

println("cool".r replaceFirstIn (str, "awesome"))
Full source at GitHub

运行结果

Some(Scala)
Scala, scala
Scala is scalable and awesome
Full source at GitHub

PatternMatching/MatchUsingRegex.scala

def process(input: String): Unit = {
  val GoogStock = """^GOOG:(\d*\.\d+)""".r
  input match {
    case GoogStock(price) ⇒ println(s"Price of GOOG is $$$price")
    case _                ⇒ println(s"not processing $input")
  }
}
process("GOOG:310.84")
process("GOOG:310")
process("IBM:84.01")
Full source at GitHub

运行结果

Price of GOOG is $310.84
not processing GOOG:310
not processing IBM:84.01
Full source at GitHub

PatternMatching/MatchUsingRegex2.scala

def process(input: String): Unit = {
  val MatchStock = """^(.+):(\d*\.\d+)""".r
  input match {
    case MatchStock("GOOG", price) ⇒ println(s"We got GOOG at $$$price")
    case MatchStock("IBM", price)  ⇒ println(s"IBM's trading at $$$price")
    case MatchStock(symbol, price) ⇒ println(s"Price of $symbol is $$$price")
    case _                         ⇒ println(s"not processing $input")
  }
}
process("GOOG:310.84")
process("IBM:84.01")
process("GE:15.96")
Full source at GitHub

运行结果

We got GOOG at $310.84
IBM's trading at $84.01
Price of GE is $15.96
Full source at GitHub