Scala学习(十二)高阶函数
2019獨角獸企業重金招聘Python工程師標準>>>
1.作為值的函數
在Scala中,你可以在變量中存放函數:
import scala.math._val num = 3.14val fun = ceil _這段代碼將num設為3.14, fun設為ceil函數。
說明:?ceil函數后的 _ 意味著你確實指的是這個函數,而不是碰巧忘記給它傳遞參數。 從技術上講, _ 將ceil方法轉成了函數,在Scala中,你無法直接操縱方法,而只能直接操縱函數。
怎么使用函數:
- 調用它
- 傳遞它,存放在變量中,或者作為參數傳遞給另一個函數
以下是如何調用存放在fun中的函數:
fun(num) // fun是一個包含函數的變量,而不是一個固定的函數以下是如何將fun傳遞給另一個函數:
Array(3.14, 1.42, 2.0).map(fun) // 將fun傳遞給另一個函數, Array(4.0, 2.0, 2.0)map方法接收一個函數參數,將它應用到數組中的所有值,然后返回結果的數組。
2.匿名函數
在Scala中,你不需要給每個函數命名,它就是匿名函數:
(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時調用第一個函數參數,否則調用第二個函數參數
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 - bval 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) // 100mulBy函數的類型為: (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 * _ // OK5.一些有用的高階函數
(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 7mulBy首次調用時將參數變量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 = 0val 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 * ydef 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): Booleanthat序列和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) {blockuntil(condition) (block)} }var x = 10 until (x == 0) {x -= 1println(x) }10.return表達式
在Scala中,函數的返回值就是函數體的值,即最后一個表達式的值。所有不需要使用return語句返回函數值。但是,你可以用return來從一個匿名函數中返回值給包含這個匿名函數的帶名函數:
def indexOf(str: String, ch: Char): Int = {var i = 0until (i == str.length) {if (str(i) == ch)return ii += 1}return -1 }如果在帶名函數中使用return語句,需要給出它的返回類型。
注:如果異常在被送往待命函數值前,在一個try代碼塊中被捕獲掉了,那么相應的值就不會返回。
轉載于:https://my.oschina.net/u/3687664/blog/2236840
總結
以上是生活随笔為你收集整理的Scala学习(十二)高阶函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入浅出java设计模式--静态代理(史
- 下一篇: 匿名函数gc分析