Scala學習(十二)高階函數

1.做爲值的函數

在Scala中,你能夠在變量中存放函數:數組

import scala.math._

val num = 3.14

val fun = ceil _

這段代碼將num設爲3.14, fun設爲ceil函數。閉包

說明: ceil函數後的 _ 意味着你確實指的是這個函數,而不是碰巧忘記給它傳遞參數。 從技術上講, _ 將ceil方法轉成了函數,在Scala中,你沒法直接操縱方法,而只能直接操縱函數。ide

怎麼使用函數:函數

  • 調用它
  • 傳遞它,存放在變量中,或者做爲參數傳遞給另外一個函數

如下是如何調用存放在fun中的函數:ui

fun(num) // fun是一個包含函數的變量,而不是一個固定的函數

如下是如何將fun傳遞給另外一個函數:spa

Array(3.14, 1.42, 2.0).map(fun) // 將fun傳遞給另外一個函數, Array(4.0, 2.0, 2.0)

map方法接收一個函數參數,將它應用到數組中的全部值,而後返回結果的數組。scala

2.匿名函數

在Scala中,你不須要給每一個函數命名,它就是匿名函數:code

(x: Double) => 3 * x
// 將這個函數存放在變量中
val triple = (x: Double) => 3 * x
// 這和用def同樣
def triple(x: Double) = 3 * x

// 做爲參數傳遞
Array(3.14, 1.42, 2.0).map((x: Double) => 3 * x)
Array(3.14, 1.42, 2.0).map((_ * 3) //或者這樣,最簡形式
Array(3.14, 1.42. 2.0).map{ (x: Double) => 3 * x } // 也可使用花括號
Array(3.14, 1.42. 2.0) map { (x: Double) => 3 * x } // 使用中置表示法

練習:定義一個函數,入參4個,前兩個數Int數字,後兩個是函數,當第一個入參大於0時調用第一個函數參數,不然調用第二個函數參數orm

def call(a:Int, b:Int, f1:(Int,Int)=>Int, f2:(Int,Int)=>Int) = {
    if(a > 0) f1(a,b) else f2(a,b)
}

def add(a:Int, b:Int) = a + b
def sub(a:Int, b:Int) = a - b

val f1 = add _
val f2 = sub _

//能夠採用以下方式調用call函數

call(1, 2, f1, f2)
call(1, 2, add _, sub _)
call(1, 2, add, sub)
call(1,2,(a:Int,b:Int)=>a+b,(a:Int,b:Int)=>a-b)

 

3.帶函數參數的函數

def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
valueAtOneQuarter(ceil _) // 1.0
valueAtOneQuarter(sqrt _) // 0.5

這裏的參數是一個接受Double並返回Double的函數。而valueAtOneQuarter的函數類型是:對象

((Double) => Double) => Double ; 一個接受函數參數的函數,它就稱做高階函數。例如valueAtOneQuarter。

高階函數也能夠產出另外一個函數,即返回一個函數:

def mulBy(factor: Double) = (x: Double) => factor * x
mulBy(3) // 返回函數 (x: Double) => 3 * x
mulBy函數的威力在於,它能夠產出可以乘以任何數額的函數:

val quintuple = mulBy(5)
quintuple(20) // 100

mulBy函數的類型爲: (Double) => ( (Double) => Double)

4.參數類型推斷

Scala有比較強大的參數推導:

def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
valueAtOneQuarter( (x: Double) => 3 * x ) // 0.75
// 能夠簡單寫成
valueAtOneQuarter( (x) => 3 * x ) // 0.75
// 只有一個參數的狀況,還能夠省卻參數的括號:
valueAtOneQuarter( x => 3 * x ) // 0.75
// 若是參數在 => 右側只出現一次,能夠用 _ 替換它
valueAtOneQuarter( 3 * _ ) // 0.75
這些簡寫方式僅在參數類型已知的狀況下有效:

val fun = 3 * _ // error
val fun = 3 * (_: Double) // OK
val fun: (Double) => Double = 3 * _ // OK

5.一些有用的高階函數

(1 to 9).map(0.1 * _) // _應用於全部元素
(1 to 9).map("*" * _).foreach(println _) //打印一個三角形

在這裏咱們還用到了foreach,它和map很像,只不過他的函數並不返回任何值

filter方法輸出全部匹配某個特定條件的元素。

(1 to 9).filter(_ % 2 == 0) // 將能被2整除的過濾出來,輸出2,4,6,8

固然,這並非獲得該結果的最高效方式

// reduceLeft方法接受一個二元的函數,將它應用到序列中的全部元素,從左到右
(1 to 9).reduceLeft(_ * _) // 1*2*3*...*8*9

等同於 1*2*3*4*5*6*7*8*9 或者更嚴格地說 ((((((((1*2)*3)*4)*5)*6)*7)*8)*9)// 排序

"Mary has a little lamb".split(" ").sortWith(_.length < _.length)

6.閉包

函數能夠在變量再也不處於做用於內時被調用。這樣的函數稱爲閉包, 閉包由代碼和代碼用到的任何非局部變量定義構成。 例如:

def mulBy(factor: Double) = (x: Double) => factor * x
// 以下調用
val triple = mulBy(3)
val half = mulBy(0.5)
println(triple(14) + " " + half(14)) // 42 7

mulBy首次調用時將參數變量factor設爲3, 該變量在(x: Double) => factor * x 函數的函數體內被引用,該函數被存入triple。而後參數變量factor從運行時的棧上被彈出;

mulBy第二次被調用時,參數變量被設爲了0.5, 該變量在(x: Double) => factor * x 函數的函數體內被引用,該函數被存入half 。

這樣,每個返回的函數都要本身的factor設置。在這裏triple和half存儲的函數訪問了它們做用於範圍外的變量。

7.SAM轉換

在Scala中,你能夠傳遞函數做爲參數,而在Java中是不能夠的(目前),其一般的作法是將動做放在一個實現某接口的類中,而後將該類的一個實例傳遞給另外一個方法。在不少時候,這些接口都只有單個抽象方法(single abstract method), 簡稱SAM類型。 例如:

var counter = 0

val button = new JButton("Increment")
button.addActionListener(new ActionListener {
    override def actionPerformed(event: ActionEvent) {
        counter += 1
    }
})

這裏使用了樣板代碼,咱們但願的是隻傳遞一個函數給addActionListener就行了:

button.addActionListener((event: ActionEvent) => counter += 1)

爲了啓用這個語法,你須要提供一個隱士轉換,由於addActionListener是Java的方法。

implicit def makeAction(action: ( (ActionEvent) => Unit ) ) = {
    new ActionListener {
        override def actionPerformed(event: ActionEvent) { action(event) }
    }
}

只須要簡單的把這個函數和你的界面代碼放在一塊兒就能夠在須要傳入ActionListener 對象的地方傳入任何(ActionEvent) => Unit 類型的函數了。

 

8.柯里化

柯里化指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個原有的函數的第二個參數做爲參數的函數。

def mul(x: Int, y: Int) = x * y

def mulOneAtAtime(x: Int) = (y: Int) => x * y // 定一個接受一個參數,生成另外一個接受一個參數的函數

mulOneAtAtime(6)(7)

這裏mulOneAtAtime(6)會返回(y: Int) => 6 * y 類型的函數,而後該函數又被調用,最終計算出結果

Scala支持以下簡寫來定義這樣的柯里化函數:

def mulOneAtAtime (x: Int) (y: Int) = x * y

一個典型應用:corresponds方法能夠比較兩個序列是否在某個條件下相同:

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序列和p函數是分開的兩個柯里化的參數,類型推斷器先推斷出that是一個String類型的序列,也就是B是String,而後才能在p函數中推斷出函數類型是(String, String) => Boolean。

9.控制抽象

對於一個沒有參數也沒有返回值的函數:

def runInThread(block: () => Unit) {
    new Thread {
        override def run() { block() }
    }.start()
}

runInThread { () => println("Hi"); Thread.sleep(1000); println("Bye") }

() => 這樣看上比不那麼美觀,要向省掉() => 可使用換名調用表示方法:在參數聲明和調用該函數參數的地方略去(),但保留 => :

def runInThread(block: => Unit) {
    new Thread {
        override def run() { block } //注意上面省略了(),這裏也要省略
    }.start()
}

在調用時咱們能夠省略() =>

runInThread { println("Hi"); Thread.sleep(1000); println("Bye") }

在例如:

def until(condition: => Boolean) (block: => Unit) {
    if (!condition) {
        block
        until(condition) (block)
    }
}

var x = 10
until (x == 0) {
    x -= 1
    println(x)
}

10.return表達式

在Scala中,函數的返回值就是函數體的值,即最後一個表達式的值。全部不須要使用return語句返回函數值。可是,你能夠用return來從一個匿名函數中返回值給包含這個匿名函數的帶名函數:

def indexOf(str: String, ch: Char): Int = {
    var i = 0
    until (i == str.length) {
        if (str(i) == ch)
            return i
        i += 1
    }
    return -1
}

若是在帶名函數中使用return語句,須要給出它的返回類型。

注:若是異常在被送往待命函數值前,在一個try代碼塊中被捕獲掉了,那麼相應的值就不會返回。

相關文章
相關標籤/搜索