hangscer

模式匹配

2017/02/11

模式匹配

在Scala中,模式匹配仅次于函数值和闭包的第二大广泛特征。

本章学习模式匹配机制,case类和提取器(extractor)。

函数式编程->模式匹配->发挥想象->无尽可能。

匹配类型 匹配个数 匹配特定位置元素 ……

9.1 匹配字面量和常量

match是一个对Any起作用的表达式。字面量和常量可以直接匹配。

1
2
3
4
5
6
def activity(day:String)= day match{
case "Sunday"=>"Eat,sleep,repeat...."
case "Saturday"=>"Hangout with friends..."
case "Monday"=>"...code for fun..."
case "Friday"=>"...read a good book..."}
List("Sunday","Saturday","Monday","Friday").map(activity(_)).foreach(println)

注意,把这里的case语句理解为一个简单函数值。

9.2 匹配通配符

上面的例子中,我们没有处理所有可能的值,则会出现MatchError异常。
使用_来匹配所有。

9.3 匹配元组和列表

消息不止单个字面量,还有元组、列表等序列。
现在编写一个服务,其中需要接受和处理地理坐标,如果把坐标表示为元组,则:

1
2
3
4
5
6
7
8
9
def processCoordinates(input:Any)=
input match {
case (a,b) => println(s"Processing $a,$b")
//这里还未涉及到类型匹配
case "done"=>println("done")
case _=>println("____null")
processCoordinates("done")
processCoordinates((1,2))
processCoordinates(null)

如何匹配List呢?

1
2
3
4
5
6
7
8
9
def processItems(items:List[String])=
items match{
case List("apple","ibm")=>println("apple ibm")
case List("red","blue","white")=>println("Starts and Stripes...")
case List("red","blue",_*)=>println("colors red ,blue,....")
case List("apple","orange",otherFruits @ _*)=>println("apples and oranges ",otherFruits)}
processItems(List("red","blue"))
// 验证_*可以匹配零个或者多个 _*让我们只需要提供需要关心的元素即可
// otherFruits @ _* 这种形式为匹配到的元素起个别名

9.4 类型和卫语句的匹配

上面的例子我们没有对类型进行匹配。

有时候,需要处理的序列,其值虽然个数相同,但是其中类型有些不同。

比如对Int序列的处理不同于Double序列处理。

在Scala中,case语句可以根据类型进行匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
def process(input:Any)=
input match{
case (a:Int,b:Int)=>println("Processing (int,int)...")
case (a:Double,b:Double)=>println("Processing (double,double)...")
case msg:Int if(msg>1000)=>println("Processing int >1000") //这里有个卫语句
case msg:Int=>println("Processing int...")
case msg:String=>println("processing string...")
case _=>println(s"Can't handle $input")}
process((2131.1,4324.3))
process(0)
process(10000000)
process(2.2)

case的顺序很重要,Scala是从上到下地求值。

9.5 case表达式里的模式变量和常量

比如匹配模式中的a和b,这些是模式变量。约定,Scala中模式变量要以小写字母开头,常量要以大写字母开头。
书上讲的不太好,用反引号``,应用实际的变量。

1
2
3
4
5
6
7
8
9
10
11
class Sample{
val max =100
val MIN=0
def process(input:Int)={
input match{
case `max`=>println("do not trythis at home") //this min reference to the val "min"
case MIN=>println("you matched MIN")
case _=>println("unrea")
}
}
}

现实世界里,我们需要更复杂的匹配模式,字面量、元组和List是远远不够的。

于是,Scala给我们两个选择:case类和提取器。

9.6 对XML片段进行匹配

xml片段(fragment)进行匹配

9.7 使用case类进行模式匹配

case class(case类)是一种特殊的类,适合用于case表达式的匹配。
假定我们需要接收和处理股票交易信息。股票名称、数量。
以下是简单使用:好比List类型一样;

1
2
3
4
5
6
7
8
9
10
11
12
13
case class Sell(stockSymbol:String,quantity:Int)
case class Buy(stockSymbol:String,quantity:Int)
case class Hedge(stockSymbol:String,quantity:Int)
class TradeProcessor{
def processTransaction(request:Any)={
request match{
case Sell(stock,1000)=>println("Selling 1000-units of "+stock)
case Sell(stock,quantity)=>println(s"Selling $stock units $quantity")
case Buy(stock,quantity) if(quantity>2000) =>println(s"Buyong $quantity (large) of $stock") //这里请注意卫语句的使用
case Buy(stock,quantity)=>println(s"Buying $quantity units of $stock")
}
}
}

仍然还是自由发挥,随心所欲

9.8 使用提取器进行匹配

提取器Extractor将模式匹配带入下一个阶段:匹配任意模式
extractor会从输入中提取出匹配的部分。
我们现在写一个服务,处理股票,接收股票代码,返回股票的价格。

1
2
3
4
5
6
7
8
9
10
11
12
13
object StockService{
def process(input:String)={
input match{
case Symbol()=>println("Look up price for valid symbol "+input)
case _=>println("Invalid input "+input)
}
}
}
object Symbol{//提取器有个方法叫做unapply(),接收要匹配的值
def unapply(symbol:String)={
symbol=="GOOD"||symbol=="IBM"
}
}

Symbol则是一个提取器,如果提取器确定股票代码无误,就返回true,否则false。如果true,就会执行同行case函数值。否则,进入下一行。

extractor有个函数叫做unapply(),接收要匹配的值。执行case Symbol()=>……时,match表达式会自动把input作为参数传入unapply(),这里语法糖,Symbol()相当于调用Symbol.unapply(),但是参数命名受限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object StockService{
def process(input:String)={
input match{
case Symbol()=>println("Look up price for valid symbol "+input)
case ReceiveStockPrice(symbol,price) =>println(s"Received price $price ,$symbol")//注意此处的symbol和price ,将类似"IBM:21312"=>("IBM",1232321)
case _=>println("Invalid input "+input)
}
}
}
object ReceiveStockPrice{
def unapply(input:String):Option[(String,Double)]={//input 形如 "IBM:123123"
if(input contains ":"){
val splitQuote=input split ":"
Some(splitQuote(0),splitQuote(1).toDouble)
}else
None
}
None
}

9.9 正则表达式