教材:快學(xué)Scala
chapter 12. 高階函數(shù) Higher-Order Functions
12.1 functions as values
- 方法(method)和函數(shù)(function)是不一樣的,method不是值,function是值
- method用def定義 function可以賦值給一個(gè)val
-
方法轉(zhuǎn)換成函數(shù):
val func = 方法 _Scala無法直接操作(manipulate)方法,只能直接操作函數(shù) - 函數(shù)的用法:1.調(diào)用 2.傳遞(到變量/到另一個(gè)函數(shù))
12.2 匿名函數(shù)
(x: Double) => 3 * x
scala> val triple = (x: Double) => 3*x // 定義函數(shù)
triple: Double => Double = <function1>
scala> def triple(x: Double) = 3*x // 定義方法
triple: (x: Double)Double
- 函數(shù)參數(shù)可以不用圓括號
()用花括號{}括住
e.g.
Array(3.14, 1.42, 2.0).map{ (x: Double) => 3 * x }其中
(x: Double) => 3 * x是函數(shù)map的參數(shù)??!
12.3 函數(shù)作為參數(shù)
- 作為參數(shù)的函數(shù),它的類型寫作
(參數(shù)類型) => 結(jié)果類型 - 帶函數(shù)參數(shù)的函數(shù)叫做高階函數(shù)(higher-order function)
12.4 參數(shù)類型推斷 Parameter Inference
- 在必要信息給出的前提下Scala可自動(dòng)推斷參數(shù)屬于什么類型
def quarter(f: (Double) => Double) = f(0.25) // 類型為((Double) => Double) => Double
quarter((x: Double) => 3 * x)
quarter((x) => 3 * x) // 從傳入?yún)?shù)類型為(Double) => Double) 知道x的類型為Double 可省略
quarter(x => 3 * x) // 只有一個(gè)參數(shù)(x) 可寫成x
quarter(3 * _) // 如果x在=>右側(cè)只出現(xiàn)一次,可以用_代替x
12.5 一些有用的高階函數(shù)
- 通用原則:如果你要的是一個(gè)序列的值,那么想辦法從一個(gè)簡單的序列轉(zhuǎn)化得出
(1 to 9).map("*" * _).foreach(println _)
(1 to 9).filter(_ % 2 == 0)// 2,4,6,8
(1 to 9).reduceLeft(_ * _)// 從左到右reduce (..((1 * 2) * 3) * ... * 9)
(1 to 9).foldLeft(100)(_ + _)// 145 與reduceLeft區(qū)別是提供了迭代初始值
"Mary has a little lamb".split(" ").sortWith(_.length < _.length)
等價(jià)于
"Mary has a little lamb".split(" ").sortWith((a: String, b: String) => a.length < b.length)
12.6 閉包 Closures
- A closure consists of code together with the definitions of any nonlocal variables that the code uses.
- Java 8支持一種形式上受限的閉包
12.7 SAM轉(zhuǎn)換
- 背景:Java不支持函數(shù)參數(shù),需要將動(dòng)作放進(jìn)一個(gè)實(shí)現(xiàn)某接口的類中,再傳遞類的實(shí)例作為參數(shù)
- 這種接口通常只有單個(gè)抽象方法(Single Abstract Method) Java中被稱為SAM類型
- 問題:如何實(shí)現(xiàn)函數(shù)參數(shù) -> SAM參數(shù)的轉(zhuǎn)換?
- 答案:隱式轉(zhuǎn)換(implicit)語法,通過自定義隱式轉(zhuǎn)換實(shí)現(xiàn)需求
implicit def makeAction(action: (ActionEvent) => Unit) = // action: 函數(shù)參數(shù)
new ActionListener { // ActionListener: 需要轉(zhuǎn)換成的實(shí)例
override def actionPerformed(event: ActionEvent) { action(event) } // actionPerformed: SAM
}
// 效果:在所有預(yù)期ActionListener對象的地方傳入任何(ActionEvent)=>Unit函數(shù)
12.8 柯里化 Currying
def mulOneAtATime(x: Int)(y: Int) = x * y
- 用途:把多參數(shù)中的某個(gè)參數(shù)單獨(dú)拎出來,提供用于類型推斷的信息
val a = Array("hello", "world")
val b = Array("Hello", "World")
a.corresponds(b)(_.equalsIgnoreCase(_))
corresponds的定義為
def corresponds[B](that: Seq[B])(p: (A, B) => Boolean): Boolean
通過that參數(shù)知道B類型,用來推斷函數(shù)p的輸入類型
12.9 控制抽象 Control Abstractions
- 函數(shù)是"頭等公民":一系列語句 -> 不帶參數(shù)也沒有返回值的函數(shù)
- "不帶參數(shù)也沒有返回值的函數(shù)" 的函數(shù)類型:
() => Unit
def runInThread(block: () => Unit) { // 輸入是一個(gè)過程(控制結(jié)構(gòu)),表現(xiàn)為一個(gè)"不帶參數(shù)也沒有返回值的函數(shù)"block
new Thread {
override def run() { block() }
}.start()
}
// 調(diào)用:{}內(nèi)的是runInThread的參數(shù),一個(gè)無參數(shù)無返回的函數(shù)
runInThread { () => println("Hi"); Thread.sleep(1000); println("Bye") }
想在調(diào)用中省略()=>寫法,使用call-by-name(換名調(diào)用)
def runInThread(block: => Unit) { // call-by-name語法:參數(shù)名:=>類型
new Thread {
override def run() { block } // block不加()
}.start()
}
// 調(diào)用:不用加()=>
runInThread { println("Hi"); Thread.sleep(1000); println("Bye") }
- call-by-name參數(shù):函數(shù)被調(diào)用的時(shí)候,參數(shù)表達(dá)式不會(huì)被求值,而在函數(shù)體內(nèi)每次出現(xiàn)都會(huì)求值一次
- call-by-value參數(shù):就是普通的參數(shù),函數(shù)被調(diào)用的時(shí)候會(huì)被求值,然后函數(shù)體內(nèi)每次出現(xiàn)都不會(huì)重新計(jì)算
def until(condition: => Boolean)(block: => Unit) {
if (!condition) {
block
until(condition)(block)
}
}
var x = 10
until (x == 0) {
x -= 1
println(x)
}
12.10 return表達(dá)式
-
return在Scala的作用:函數(shù)A的函數(shù)體內(nèi)定義了匿名函數(shù)B,若B的函數(shù)體內(nèi)使用return語句,則直接跳出A
def indexOf(str: String, ch: Char): Int = {
var i = 0
until (i == str.length) {
if (str(i) == ch) return i // 直接跳出indexOf
i += 1
}
return -1
}
練習(xí)答案
def values(fun: (Int) => Int, low: Int, high: Int) = (low to high) zip ((low to high).map(fun))reduceLeft(max(_,_))fibo(x: Int) = if(x != 0) (1 to x).reduceLeft(_*_) else 1fibo(x: Int) = (1 to x).foldLeft(1)(_*_)def largest(fun:(Int)=>Int, inputs:Seq[Int]) = inputs.map(fun).reduceLeft(max(_,_))def largestAt(fun:(Int)=>Int, inputs:Seq[Int]) = inputs.zip(inputs.map(fun)).reduceLeft((a, b) => if (a._2 > b._2) a else b )._1
def adjustToPair[A, B, C](fun: (A, B) => C) = (pair: (A, B)) => fun(pair._1, pair._2)
(1 to 10).zip((11 to 20)).map(adjustToPair[Int, Int, Int](_+_))
Array("i", "am", "cat").corresponds(Array(1, 2, 3))(_.length == _)
def myCorresponds[A, B](iThis: Seq[A], iThat: Seq[B], p: (A, B) => Boolean): Boolean = {
val pairs = iThis zip iThat
// pairs.map(adjustToPair[A, B, Boolean](p)).filter(_ == false).length == 0
// 看完13.8以后學(xué)到的新姿勢
pairs.map(adjustToPair[A, B, Boolean](p)).forall(_ == true)
}
def unless(condition: => Boolean)(block: => Unit) = if (!(condition)) { block }