第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 }

运行结果

...code for fun...Eat, sleep, repeat... Hang out with friends...

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)

运行结果

Hang out with friends
...code for fun...

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")

运行结果

Processing (39, -104)...done

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"))

运行结果

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

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)

运行结果

Processing (double, double)... Processing int... Processing int > 1000000
Can't handle 2.2...

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)

运行结果

You matched max 0
You matched max 100

PatternMatching/MatchWithField1.scala

case this.max => println(s"You matched max $max")

运行结果

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

PatternMatching/MatchWithField2.scala

case `max` => println(s"You matched max $max")

运行结果

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

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)

运行结果

scala.MatchError: 0 (of class java.lang.Integer)
You matched max

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

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")
    }
  }
}

PatternMatching/TradeStock.scala

TradeProcessor.processTransaction(Sell("GOOG", 500))
TradeProcessor.processTransaction(Buy("GOOG", 700))
TradeProcessor.processTransaction(Sell("GOOG", 1000))
TradeProcessor.processTransaction(Buy("GOOG", 3000))

运行结果

Selling 500 units of GOOG
Buying 700 units of GOOG
Selling 1000-units of GOOG
Buying 3000 (large) units of GOOG

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")
    }
  }
}

PatternMatching/ThingsAcceptor.scala

ThingsAcceptor.acceptStuff(Apple())
ThingsAcceptor.acceptStuff(Book())
ThingsAcceptor.acceptStuff(Apple)

运行结果

Thanks for the Apple
Thanks for the Book
Excuse me, why did you send me Apple

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;

9.4 提取器和正则表达式

PatternMatching/Extractor1.scala

StockService.process("GOOG")
StockService.process("IBM")
StockService.process("ERR")

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")
    }
  }
}

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"
  }
}

运行结果

Look up price for valid symbol GOOG
Look up price for valid symbol IBM
Invalid input ERR

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")
    }
  }
}

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
    }
  }
}

PatternMatching/Extractor.scala

StockService.process("GOOG")
StockService.process("GOOG:310.84")
StockService.process("GOOG:BUY")
StockService.process("ERR:12.21")

运行结果

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

PatternMatching/Extractor2.scala

case ReceiveStockPrice(symbol @ Symbol(), price) =>
  println(s"Received price $$$price for symbol $symbol")

运行结果

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

PatternMatching/RegularExpr.scala

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

PatternMatching/RegularExpr.scala

println(pattern.findAllIn(str).mkString(", "))

PatternMatching/RegularExpr.scala

println("cool".r.replaceFirstIn(str, "awesome"))

运行结果

Some(Scala)
Scala, scala
Scala is scalable and awesome

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")

运行结果

Price of GOOG is $310.84
not processing GOOG:310
not processing IBM:84.01

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")

运行结果

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