偏函数

  • 偏函数是它们并不处理所有可能的输入,而只处理那些能与至少一个case语句匹配的输入。
  • 在偏函数中只能使用case语句,而整个函数必须用花括号包围。如果偏函数被调用,而函数的输入却与所有语句都不匹配,系统就会抛出一个MatchError运行时错误。
  • 偏函数可以使用链式连接:pf1 orElse pf2 orElse pf3 ...

In [1]:
val pf1: PartialFunction[Any, String] = { case s: String => "YES" }
val pf2: PartialFunction[Any, String] = { case d: Double => "YES" }
val pf = pf1 orElse pf2

def tryPF(x: Any, f: PartialFunction[Any, String]): String = {
    try {
        f(x).toString
    }
    catch {
        case _: MatchError => "ERROR!"
    }
}

def d(x: Any, f: PartialFunction[Any, String]) = 
    f.isDefinedAt(x).toString


pf1: PartialFunction[Any, String] = <function1>
pf2: PartialFunction[Any, String] = <function1>
pf: PartialFunction[Any, String] = <function1>
defined function tryPF
defined function d

In [2]:
println("      |  pf1 - String  |  pf2 - Double  |  pf - All")
println("  x   | def?  | pf1(x) | def?  | pf2(x) | def? | pf(x)")
println("++++++++++++++++++++++++++++++++++++++++++++++++++++++")
List("str", 3.14, 10) foreach { x =>
    printf("%-5s | %-5s | %-6s | %-5s | %-6s | %-5s | %-6s\n", x.toString,
          d(x, pf1), tryPF(x, pf1), d(x, pf2), tryPF(x, pf2), d(x, pf), tryPF(x, pf))
}


      |  pf1 - String  |  pf2 - Double  |  pf - All

  x   | def?  | pf1(x) | def?  | pf2(x) | def? | pf(x)
++++++++++++++++++++++++++++++++++++++++++++++++++++++
str   | true  | YES    | false | ERROR! | true  | YES   
3.14  | false | ERROR! | true  | YES    | true  | YES   
10    | false | ERROR! | false | ERROR! | false | ERROR!

Future简介

scala.concurrent.Future是scala提供的并发工具,但任务封装在Future中执行时,该任务是异步的


In [3]:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global


import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

In [4]:
def sleep(millis: Long) = Thread.sleep(millis)

def doWork(index: Int) = {
    sleep((math.random * 1000).toLong)
    index
}

(1 to 5) foreach { index =>
    val future = Future {
        doWork(index)
    }
                  
    future onSuccess {
        case answer: Int => println(s"Success! returned: $answer")
    }
    future onFailure {
        case th: Throwable => println(s"FAILURE! returned: $th")
    }
}

sleep(1000)
println("Done")


Success! returned: 2
Success! returned: 3
Success! returned: 1
Success! returned: 4
Done
defined function sleep
defined function doWork
  • 调用Future单例对象的工厂方法,传入一个匿名函数,表示需要做的工作
  • Future.apply返回新的Future对象,控制权还给循环,该对象在另一个线程中执行doWork
  • onSuccess注册一个回调函数,该回调函数是一个偏函数,当future成功执行完毕后,将执行该回调

Future API的配置隐含参数

上面代码中调用的三个方法中第二个参数列表具有隐含的ExecutionContext参数

//Future.apply
apply[T](body: => T)(implicit executor: ExecutionContext): Future[T]

def onSuccess[U](func: (Try[T]) => U)(implicit executor: ExecutionContext): Unit
def onFailure[U](callback: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit

Future通过ExecutionContext来配置并发操作的执行,默认情况使用Java的ForkJoinPool来设置管理Java线程池。

斐波那契数列使用的技巧


In [5]:
import scala.annotation.tailrec

def factorial(i: Int): Long = {
    @tailrec
    def fact(i: Int, accumulator: Int): Long = {
        if (i <= 1) accumulator
        else fact(i-1, i*accumulator)
    }
    
    fact(i, 1)
}

(0 to 5) foreach (i => println(factorial(i)))


1
1
2
6
24
120
import scala.annotation.tailrec
defined function factorial
  • factorial使用了嵌套函数fact作为辅助函数
  • 由于阶乘计算结果增长快,使用Long作为返回值类型
  • 使用tailrec关键字让编译器检查自己是否对尾递归执行了优化,避免栈空间崩溃

In [6]:
// 不是尾递归的情况,编译器将抛出错误
@tailrec
def f(i: Int): Long = {
    if(i <= 1) 1L
    else f(i-2) + f(i-1)
}


Compilation Failed
Main.scala:81: could not optimize @tailrec annotated method f: it is neither private nor final so can be overridden

def f(i: Int): Long = {
    ^

类型推断

显式类型注解的场景:

  • 声明变量却没有初始化的情况(抽象声明)
  • 方法参数
  • 方法返回值类型:
    • 方法中明确使用了return
    • 递归方法
    • 多个方法重载,其中一个方法调用了另一个重载方法,调用者需要显示类型注解
    • Scala推断出的类型比你期望的类型更为宽泛

Option、Some和None,避免使用null

Option允许我们通过Some和None显式表示“有值”或“没有值”,通过类型检查避免空指针异常(null)的情况。


In [7]:
val stateCapitals = Map(
    "Alabama" -> "Montgomery",
    "Alaska" -> "Juneau",
    "Wyoming" -> "Cheyenne"
)


stateCapitals: Map[String, String] = Map(
  "Alabama" -> "Montgomery",
  "Alaska" -> "Juneau",
  "Wyoming" -> "Cheyenne"
)

In [8]:
println("Get the capitals wrapped in Options:")
println("Alabama: " + stateCapitals.get("Alabama"))
println("Wyoming: " + stateCapitals.get("Wyoming"))
println("Unknown: " + stateCapitals .get("Unknown"))

println("Get the capitals themselves out of Options:")
println("Alabama: " + stateCapitals.get("Alabama").get)
println("Wyoming: " + stateCapitals.get("Wyoming").getOrElse("Oops!"))
println("Unknown: " + stateCapitals .get("Unknown").getOrElse("Oops!"))


Get the capitals wrapped in Options:
Alabama: Some(Montgomery)
Wyoming: Some(Cheyenne)
Unknown: None
Get the capitals themselves out of Options:
Alabama: Montgomery
Wyoming: Cheyenne
Unknown: Oops!

Map.get方法返回了Option[T],对于给定的key,当对应的值可能并不存在的情况,Option已经包含在方法返回的类型中了。

Option有get或getOrElse方法,其中None.get会抛出一个NoSuchElementException异常。更安全的方法是使用getOrElse,其参数起到默认值的作用,避免潜在的异常。

参数化类型

Scala使用方括号表示参数化类型,并通过+、-号表示协类型和逆类型。


In [9]:
import java.io._

abstract class BulkReader[In] {
    val source: In
    def read: String
}

class StringBulkReader(val source: String) extends BulkReader[String] {
    def read: String = source
}

class FileBulkReader(val source: File) extends BulkReader[File] {
    def read: String = {
        val in = new BufferedInputStream(new FileInputStream(source))
        val numBytes = in.available()
        val bytes = new Array[Byte](numBytes)
        in.read(bytes, 0, numBytes)
        new String(bytes)
    }
}


import java.io._
defined class BulkReader
defined class StringBulkReader
defined class FileBulkReader

In [10]:
println(new StringBulkReader("Hello Scala!").read)
println(new FileBulkReader(new File("fileBulkReaderTestFile.txt")).read)


Hello Scala!
this is a file for read method of FileBulkReader class
do some work