您的位置:首页 > 其它

Scala Cookbook读书笔记 Chapter 3.Control Structures 第二部分

2016-09-18 12:08 531 查看

3.8 使用一个case语句匹配复杂条件

几个匹配条件要求执行相同的业务逻辑,而不是使用多个case重复业务逻辑,想要使用的是匹配条件的业务逻辑的复制。

3.8.1 解决方案

使用 | 分隔符将相同的业务逻辑的匹配条件放置到同一行上

val i = 5
i match {
case 1 | 3 | 5 | 7 | 9 => println("odd")
case 2 | 4 | 6 | 8 | 10 => println("even")
}


其他类型也适用:

val cmd = "stop"
cmd match {
case "start" | "go" => println("starting")
case "stop" | "quit" | "exit" => println("stopping")
case _ => println("doing nothing")
}


匹配多个case对象:

trait Command
case object Start extends Command
case object Go extends Command
case object Stop extends Command
case object Whoa extends Command

def executeCommand(cmd: Command) = cmd match {
case Start | Go => start()
case Stop | Whoa => stop()
}


3.9 分配匹配表达式结果给变量

问题:从匹配表达式返回值然后分配给一个变量,或者使用匹配表达式作为一个方法的内容。

3.9.1 解决方案

在匹配表达式前插入变量:

val evenOrOdd = someNumber match {
case 1 | 3 | 5 | 7 | 9 => println("odd")
case 2 | 4 | 6 | 8 | 10 => println("even")
}


这种方式一般用来创建短方法和函数

def isTrue(a: Any) = a match {
case 0 | "" => false
case _ => true
}


查看更多

Expression-oriented programming language

3.10 获取匹配表达式中默认case的值

问题:想要获取默认值,但是使用_下划线进行匹配时不能获取值。

3.10.1 解决方案

给默认case分配一个变量名代替使用_下划线,可以在语句的右边获取变量

i match {
case 0 => println("1")
case 1 => println("2")
case default => println("You gave me: " + default)
}


3.10.2 讨论

本章关键是使用一个变量名代表默认匹配,代替使用下划线_字符

分配的名字可以是任何合法的变量名:

i match {
case 0 => println("1")
case 1 => println("2")
case whoa => println("You gave me: " + whoa)
}


必须提供一个默认匹配,不提供会产生MatchError错误:

scala> 3 match {
| case 1 => println("one")
| case 2 => println("two")
| // no default match
| }
scala.MatchError: 3 (of class java.lang.Integer)
many more lines of output ...


3.11 在匹配表达式里使用模式匹配

问题:需要在一个匹配表达式里匹配一个或者多个模式,并且模式可能是常量模式,变量模式,构造函数模式,序列模式,元组模式,或者类型模式

3.11.1 解决方案

给想要匹配的每个模式定义一个case语句:

def echoWhatYouGaveMe(x: Any): String = x match {

// constant patterns
case 0 => "zero"
case true => "true"
case "hello" => "you said 'hello'"
case Nil => "an empty List"

// sequence patterns
case List(0, _, _) => "a three-element list with 0 as the first element"
case List(1, _*) => "a list beginning with 1, having any number of elements"
case Vector(1, _*) => "a vector starting with 1, having any number of elements"

// tuples
case (a, b) => s"got $a and $b"
case (a, b, c) => s"got $a, $b, and $c"

// constructor patterns
case Person(first, "Alexander") => s"found an Alexander, first name = $first"
case Dog("Suka") => "found a dog named Suka"

// typed patterns
case s: String => s"you gave me this string: $s"
case i: Int => s"thanks for the int: $i"
case f: Float => s"thanks for the float: $f"
case a: Array[Int] => s"an array of int: ${a.mkString(",")}"
case as: Array[String] => s"an array of strings: ${as.mkString(",")}"
case d: Dog => s"dog: ${d.name}"
case list: List[_] => s"thanks for the List: $list"
case m: Map[_, _] => m.toString

// the default wildcard pattern
case _ => "Unknown"
}


测试上面的匹配表达式:

object LargeMatchTest extends App {

case class Person(firstName: String, lastName: String)
case class Dog(name: String)

// trigger the constant patterns
println(echoWhatYouGaveMe(0))
println(echoWhatYouGaveMe(true))
println(echoWhatYouGaveMe("hello"))
println(echoWhatYouGaveMe(Nil))

// trigger the sequence patterns
println(echoWhatYouGaveMe(List(0,1,2)))
println(echoWhatYouGaveMe(List(1,2)))
println(echoWhatYouGaveMe(List(1,2,3)))
println(echoWhatYouGaveMe(Vector(1,2,3)))

// trigger the tuple patterns
println(echoWhatYouGaveMe((1,2))) // two element tuple
println(echoWhatYouGaveMe((1,2,3))) // three element tuple

// trigger the constructor patterns
println(echoWhatYouGaveMe(Person("Melissa", "Alexander")))
println(echoWhatYouGaveMe(Dog("Suka")))

// trigger the typed patterns
println(echoWhatYouGaveMe("Hello, world"))
println(echoWhatYouGaveMe(42))
println(echoWhatYouGaveMe(42F))
println(echoWhatYouGaveMe(Array(1,2,3)))
println(echoWhatYouGaveMe(Array("coffee", "apple pie")))
println(echoWhatYouGaveMe(Dog("Fido")))
println(echoWhatYouGaveMe(List("apple", "banana")))
println(echoWhatYouGaveMe(Map(1->"Al", 2->"Alexander")))

// trigger the wildcard pattern
println(echoWhatYouGaveMe("33d"))
}


输出如下

zero
true
you said 'hello'
an empty List

a three-element list with 0 as the first element
a list beginning with 1 and having any number of elements
a list beginning with 1 and having any number of elements
a vector beginning with 1 and having any number of elements
a list beginning with 1 and having any number of elements

got 1 and 2
got 1, 2, and 3

found an Alexander, first name = Melissa
found a dog named Suka

you gave me this string: Hello, world
thanks for the int: 42
thanks for the float: 42.0
an array of int: 1,2,3
an array of strings: coffee,apple pie
dog: Fido
thanks for the List: List(apple, banana)
Map(1 -> Al, 2 -> Alexander)

you gave me this string: 33d


在匹配表达式中,List和Map语句写成如下:

case list: List[_] => s"thanks for the List: $list"
case m: Map[_, _] => m.toString


也可以使用下面方式替代:

case list: List[x] => s"thanks for the List: $list"
case m: Map[a, b] => m.toString


更偏爱使用下划线方法,因为代码清晰不需要关心存储在List和Map中的值是什么。当然有时需要知道存储在List和Map中的值是什么,但是由于JVM中的类型擦除,那将是一个困难的问题。

如果List表达式写成如下:

case l: List[Int] => "List"


如果熟悉java平台的类型擦除,就会知道这个例子不会工作,Scala编译器会有如下警告:

Test1.scala:7: warning: non-variable type argument Int in type pattern
List[Int] is unchecked since it is eliminated by erasure
case l: List[Int] => "List[Int]"
^


如果对类型擦除不熟悉,请移步查看更多

3.11.2 讨论

通常使用这种技术时,你的方法将期待一个继承基类或者特性的实例,然后case语句将调用那个基类的子类。如echoWhatYouGaveMe方法中,每个Scala的类型都是Any的子类。

Blue Parrot application,以随机间隔播放音乐文件或者朗读文档,有以下方法:

import java.io.File

sealed trait RandomThing

case class RandomFile(f: File) extends RandomThing
case class RandomString(s: String) extends RandomThing

class RandomNoiseMaker {

def makeRandomNoise(t: RandomThing) = t match {
case RandomFile(f) => playSoundFile(f)
case RandomString(s) => speak(s)
}
}


makeRandomNoise方法声明采用RandomThing类型做参数,然后匹配表达式处理两个子类,RandomFile和RandomString。

3.11.3 模式

常量模式:常量模式只能匹配自己,任何文本都可以作为常量,如果指定0作为文本,那么只有整形 0 将会匹配

case 0 => "zero"
case true => "true"


变量模式:上面例子中没有展示变量模式,在3.10中进行了讨论,变量模式如下划线字符一样匹配任何对象。Scala绑定变量为任何对象,在case语句的右边可以使用这个变量。

case _ => s"Hmm, you gave me something ..."

//使用变量模式代替上面
case foo => s"Hmm, you gave me a $foo"


构造函数模式:可以在case语句中匹配构造函数,可以根据构造函数需要指定常量和变量模式:

case Person(first, "Alexander") => s"found an Alexander, first name = $first"
case Dog("Suka") => "found a dog named Suka"


序列模式:可以匹配List,Array,Vector等序列。使用下划线 _ 字符代表序列的一个元素,使用 _* 代表 “0或多个字符”:

case List(0, _, _) => "a three-element list with 0 as the first element"
case List(1, _*) => "a list beginning with 1, having any number of elements"
case Vector(1, _*) => "a vector beginning with 1 and having any number …"


元组模式:匹配元祖模式并且获取元祖中每个元素的值,如果不关心一个元素的值可以使用下划线 _ 代替:

case (a, b, c) => s"3-elem tuple, with values $a, $b, and $c"
case (a, b, c, _) => s"4-elem tuple: got $a, $b, and $c"


类型模式:下面的例子中str: String是一个类型模式,str是一个模式变量,可以在声明之后在表达式右边获取模式变量:

case str: String => s"you gave me this string: $str"


3.11.4 给模式添加变量

通过以下语法给模式添加变量

variableName @ pattern


如 Programming in Scala 这本书描述,“这个提供了一个变量绑定模式。这样一个模式的意义是正常执行模式匹配,如果模式成功,就像一个简单变量模式设置变量给匹配对象”。

下面通过演示问题解决来说明用处,假设有一个List模式:

case List(1, _*) => "a list beginning with 1, having any number of elements"


上面例子无法再表达式右侧获取list。想要获取list时,可以用下面方法:

case list: List[_] => s"thanks for the List: $list"


所以看上去应该尝试用一个序列模式:

case list: List(1, _*) => s"thanks for the List: $list"

//编译错误
Test2.scala:22: error: '=>' expected but '(' found.
case list: List(1, _*) => s"thanks for the List: $list"
^
one error found


解决方案是在序列模式添加一个绑定变量模式

case list @ List(1, _*) => s"$list"


下面更多演示:

case class Person(firstName: String, lastName: String)

object Test2 extends App {

def matchType(x: Any): String = x match {

//case x: List(1, _*) => s"$x" // doesn't compile
case x @ List(1, _*) => s"$x" // works; prints the list

//case Some(_) => "got a Some" // works, but can't access the Some
//case Some(x) => s"$x" // works, returns "foo"
case x @ Some(_) => s"$x" // works, returns "Some(foo)"

case p @ Person(first, "Doe") => s"$p" // works, returns "Person(John,Doe)"
}

println(matchType(List(1,2,3))) // prints "List(1, 2, 3)"
println(matchType(Some("foo"))) // prints "Some(foo)"
println(matchType(Person("John", "Doe"))) // prints "Person(John,Doe)"
}


3.11.5 匹配表达式中使用Some和None

可能会经常在匹配表达式中使用Some和None。假定有一个toInt方法定义:

def toInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s.trim))
} catch {
case e: Exception => None
}
}


使用方法:

toInt("42") match {
case Some(i) => println(i)
case None => println("That wasn't an Int.")
}


查看更多

How do I get around type erasure on Scala? Or, why can’t I get the type parameter of my collections?

My Blue Parrot application

The “Type Erasure” documentation

3.12 在匹配表达式中使用Case类

问题:想要在匹配表达式中匹配不同的case类或case对象,比如在actor里接收信息时。

3.12.1 解决方案

使用不同的模式匹配case类和对象

trait Animal
case class Dog(name: String) extends Animal
case class Cat(name: String) extends Animal
case object Woodpecker extends Animal

object CaseClassTest extends App {

def determineType(x: Animal): String = x match {
case Dog(moniker) => "Got a Dog, name = " + moniker
case _:Cat => "Got a Cat (ignoring the name)"
case Woodpecker => "That was a Woodpecker"
case _ => "That was something else"
}

println(determineType(new Dog("Rocky")))
println(determineType(new Cat("Rusty the Cat")))
println(determineType(Woodpecker))
}

//输出
Got a Dog, name = Rocky
Got a Cat (ignoring the name)
That was a Woodpecker


3.13 Case语句中添加if表达式

问题:match表达式中给case语句添加限定逻辑,比如数字范围或者匹配模式,只要模式匹配一些额外的标准

3.13.1 解决方案

case语句中添加if判断

//匹配数字范围
i match {
case a if 0 to 9 contains a => println("0-9 range: " + a)
case b if 10 to 19 contains b => println("10-19 range: " + b)
case c if 20 to 29 contains c => println("20-29 range: " + c)
case _ => println("Hmmm...")
}

//匹配一个对象的不同值
num match {
case x if x == 1 => println("one, a lonely number")
case x if (x == 2 || x == 3) => println(x)
case _ => println("some other value")
}


可以在if判断里引用类字段,假设如下x是Stock类的实例,并且有symbol和price字段

stock match {
case x if (x.symbol == "XYZ" && x.price < 20) => buy(x)
case x if (x.symbol == "XYZ" && x.price > 50) => sell(x)
case _ => // do nothing
}


可以从case类里提取字段并应用在if判断里

def speak(p: Person) = p match {
case Person(name) if name == "Fred" => println("Yubba dubba doo")
case Person(name) if name == "Bam Bam" => println("Bam bam!")
case _ => println("Watch the Flintstones!")
}


3.13.2 讨论

注意所有的例子都可以如下把if判断放在表达式的右侧:

case Person(name) =>
if (name == "Fred") println("Yubba dubba doo")
else if (name == "Bam Bam") println("Bam bam!")


但是在很多情况下,为了代码更简洁易读会直接在case语句中加入if判断,而不是右边

3.14 使用一个match表达式代替isInstanceOf

问题:需要匹配一个类型或者多个不同的类型

3.14.1 解决方案

可以使用isInstanceOf方法测试一个对象的类型

if (x.isInstanceOf[Foo]) { do something ...


然后一些编程开发人员并不鼓励使用这种方法,并且在其他情况下会更加复杂。下面的例子中可以在一个match表达式中处理不同的类型

//确定对象是否是Person的实例
def isPerson(x: Any): Boolean = x match {
case p: Person => true
case _ => false
}

//处理不同的子类
trait SentientBeing
trait Animal extends SentientBeing
case class Dog(name: String) extends Animal
case class Person(name: String, age: Int) extends SentientBeing

// later in the code ...
def printInfo(x: SentientBeing) = x match {
case Person(name, age) => // handle the Person
case Dog(name) => // handle the Dog
}


3.14.2 讨论

匹配多个类型时使用match表达式替代isInstanceOf是自然而然的事

简单例子中,isInstanceOf方法匹配一个对象更简单点:

if (o.isInstanceOf[Person]) { // handle this ...


但是伴随更多需求,match表达式比if/else语句可读性更高

3.15 在match表达式中使用List

问题:List数据结构和其他集合数据结构有所不同,它从Cons单元(Cons cells)创建,结束于一个Nil元素。match表达式中使用这个会更好,比如写一个递归函数时。

3.15.1 解决方案

创建一个List

val x = List(1, 2, 3)


使用cons单元和一个Nil元素创建List

val y = 1 :: 2 :: 3 :: Nil

写一个递归算法时,可以利用List的最后一个元素是一个Nil对象:

def listToString(list: List[String]): String = list match {
case s :: rest => s + " " + listToString(rest)
case Nil => ""
}


REPL中运行:

scala> val fruits = "Apples" :: "Bananas" :: "Oranges" :: Nil
fruits: List[java.lang.String] = List(Apples, Bananas, Oranges)

scala> listToString(fruits)
res0: String = "Apples Bananas Oranges "


这种一个case处理Nil,一个case处理List其他部分的方法也可以用在List的其他类型上:

def sum(list: List[Int]): Int = list match {
case Nil => 1
case n :: rest => n + sum(rest)
}

def multiply(list: List[Int]): Int = list match {
case Nil => 1
case n :: rest => n * multiply(rest)
}


REPL中运行

scala> val nums = List(1,2,3,4,5)
nums: List[Int] = List(1, 2, 3, 4, 5)

scala> sum(nums)
res0: Int = 16

scala> multiply(nums)
res1: Int = 120


3.15.2 讨论

记住必须处理Nil这个case,不然会报错:

REPL中:

warning: match is not exhaustive! (完整)


正式项目中:

Exception in thread "main" scala.MatchError: List()
(of class scala.collection.immutable.Nil$)


3.16 在try/catch中匹配一个或更多异常

问题:在一个try/catch语句块中捕获一个或者更多异常

3.16.1 解决方案

Scala的try/catch/finally语法和Java很像,但是他在catch语句块中使用match表达式:

val s = "Foo"
try {
val i = s.toInt
} catch {
case e: Exception => e.printStackTrace
}


捕获更多异常:

try {
openAndReadAFile(filename)
} catch {
case e: FileNotFoundException => println("Couldn't find that file.")
case e: IOException => println("Had an IOException trying to read that file")
}


3.16.2 讨论

如果不关心具体异常,想要捕获所有异常并进行处理:

try {
openAndReadAFile("foo")
} catch {
case t: Throwable => t.printStackTrace()
}


捕获所有并且忽略不进行信息处理:

try {
val i = s.toInt
} catch {
case _: Throwable => println("exception ignored")
}


在Java中,可以从一个catch语句抛出一个异常。但是因为Scala没有已检查的异常,不需要指定一个方法抛出异常。下面演示方法不使用注解:

// nothing required here
def toInt(s: String): Option[Int] =
try {
Some(s.toInt)
} catch {
case e: Exception => throw e
}


如果喜欢声明方法抛出的异常或者需要和Java进行交互,添加@throws注解

@throws(classOf[NumberFormatException])
def toInt(s: String): Option[Int] =
try {
Some(s.toInt)
} catch {
case e: NumberFormatException => throw e
}


3.17 在try/catch/finally语句块里使用变量之前先声明它

问题:想要在try语句里使用一个对象,然后在finally部分获取这个对象,例如需要在一个对象上调用一个关闭的方法。

3.17.1 解决方案

在try/catch语句块之前声明字段为Option类型,然后在try语句内创建一个Some:

import java.io._

object CopyBytes extends App {

var in = None: Option[FileInputStream]
var out = None: Option[FileOutputStream]

try {
in = Some(new FileInputStream("/tmp/Test.class"))
out = Some(new FileOutputStream("/tmp/Test.class.copy"))
var c = 0
while ({c = in.get.read; c != −1}) {
out.get.write(c)
}
} catch {
case e: IOException => e.printStackTrace
} finally {
println("entered finally ...")
if (in.isDefined) in.get.close
if (out.isDefined) out.get.close
}

}


通常会告诉别人不要使用Option的get和isDefined方法,但是这是为数不多的一次认为这种使用是可以接受的,并且代码更可读

另一种方法是使用foreach方法:

try {
in = Some(new FileInputStream("/tmp/Test.class"))
out = Some(new FileOutputStream("/tmp/Test.class.copy"))
in.foreach { inputStream =>
out.foreach { outputStream =>
var c = 0
while ({c = inputStream.read; c != −1}) {
outputStream.write(c)
}
}
}
} // ...


上面方法两个变量时仍然可读,并且避免了get方法的调用。但是更多变量时并不实用。

3.17.2 讨论

声明Option字段的关键点在于没有初始化赋值:

var in = None: Option[FileInputStream]
var out = None: Option[FileOutputStream]


可以通过以下方式理解:

var x has No Option[yeT]
var x = None: Option[Type]


下面演示声明变量为null的处理,但是Scala中完全不需要考虑null值,所以下面的方法并不推荐:

// (1) declare the null variables
var store: Store = null
var inbox: Folder = null

try {
// (2) use the variables/fields in the try block
store = session.getStore("imaps")
inbox = getFolder(store, "INBOX")
// rest of the code here ...
} catch {
case e: NoSuchProviderException => e.printStackTrace
case me: MessagingException => me.printStackTrace
} finally {
// (3) call close() on the objects in the finally clause
if (inbox != null) inbox.close
if (store != null) store.close
}


查看更多

this Oracle “Byte Streams” example

3.18 创建自己的控制结构体

问题:自定义控制结构体改善Scala语言,简化代码,或者创建DSL给其他人使用

3.18.1 解决方案

Scala语言创建者有意不实现一些关键词,反而通过Scala库实现功能。比如3.5章节的“Implementing break and continue”,尽管Scala语言没有break和continue关键词,同样可以通过库里的方法实现同样的功能。

下面例子演示,假设不喜欢while循环,创建自己的whilst循环,可以像下面方法使用:

package foo

import com.alvinalexander.controls.Whilst._

object WhilstDemo extends App {

var i = 0
whilst (i < 5) {
println(i)
i += 1
}

}


创建whilst控制结构体,定义一个叫做whilst的函数并且需要两个参数,第一个参数处理测试条件(i < 5),第二个参数运行用户代码块。

可以通过包裹while操作来实现方法:

// 1st attempt
def whilst(testCondition: => Boolean)(codeBlock: => Unit) {
while (testCondition) {
codeBlock
}
}


更好的方法是不调用while:

package com.alvinalexander.controls

import scala.annotation.tailrec

object Whilst {

// 2nd attempt
@tailrec
def whilst(testCondition: => Boolean)(codeBlock: => Unit) {
if (testCondition) {
codeBlock
whilst(testCondition)(codeBlock)
}
}

}


3.18.2 讨论

第二个例子中使用了递归调用,但是在其他简单例子中不需要递归。假设需要一个执行两个条件判断的控制结构体。如果两个都为true,则运行代码块。表达式可能如下方法调用:

doubleif(age > 18)(numAccidents == 0) { println("Discount!") }


定义一个三个参数的函数:

// two 'if' condition tests
def doubleif(test1: => Boolean)(test2: => Boolean)(codeBlock: => Unit) {
if (test1 && test2) {
codeBlock
}
}


查看更多

The ‘using’ control structure in Beginning Scala (by David Pollak)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息