Scala - 08 - 函數語言程式設計:高階函式
阿新 • • 發佈:2018-11-30
函數語言程式設計的崛起
函數語言程式設計中的 “值不可變性”避免了對公共的可變狀態進行同步訪問控制的複雜問題,能夠較好滿足分散式並行程式設計的需求,適應大資料時代的到來。函式是第一等公民
- 可以作為實參傳遞給另外一個函式
- 可以作為返回值
- 可以賦值給變數
- 可以儲存在資料結構裡
def greeting() = (name: String) => { s"Hello" + " " + name } //> greeting: ()String => String greeting()("World") //> res0: String = Hello World def greeting2(age: Int) = (name: String) => { s"Hello $name, your age is $age" } //> greeting2: (age: Int)String => String greeting2(29)("Anliven") //> res2: String = Hello Anliven, your age is 29
函式型別和值
在函數語言程式設計中,函式的使用方式和其他資料型別的使用方式完全一致,可以像任何其他資料型別一樣被傳遞和操作。 也就是說,可以像定義變數那樣去定義一個函式,函式也會有“值”,函式的“值”就是“ 函式字面量(Funciton Literal)”,也稱為 函式文字量或 函式常量。實際上,這個函式字面量其實就是一個 匿名函式。 需要注意:- Scala語法要求函式的“值”採用“=>”而不是“=”。
- 在Scala中,函式型別的格式為 A => B,表示一個接受型別A的引數,並返回型別B的函式。
def test(x: Int): Int = { x + 1 } //> test: (x: Int)Int def test1(x: Int) = x + 1 //> test1: (x: Int)Int val test2: Int => Int = { (x: Int) => x + 1 } //> test2 : Int => Int = testrepl$$$Lambda$8/[email protected] val test3 = { (x: Int) => x + 1 } //> test3 : Int => Int = testrepl$$$Lambda$9/示例說明: 第7行把函式"(x: Int, y: Int) => x + y"作為一個值(函式字面量)賦給test6變數。 由此可見,Scala中的函式和普通變數的使用方式完全一致。[email protected] def test5(x: Int, y: Int) = x + y //> test5: (x: Int, y: Int)Int var test6 = (x: Int, y: Int) => x + y //> test6 : (Int, Int) => Int = testrepl$$$Lambda$10/[email protected] test6(2, 6)
匿名函式
使用匿名函式(Anonymous Function),可以不需要給每個函式命名,大大簡化程式碼編寫工作。 匿名函式的定義形式(也稱為“ Lamda表示式”): (引數) => {表示式} 注意:如果引數只有一個,可省略引數的圓括號;如果表示式只有一行,可省略表示式的花括號。val myNum: Int => Int = (x: Int) => { x * 2 } //> myNum : Int => Int = testrepl$$$Lambda$3/[email protected] val myNum2 = (x: Int) => x * 2 //> myNum2 : Int => Int = testrepl$$$Lambda$9/[email protected] val myNum3: Int => Int = (x) => x * 2 //> myNum3 : Int => Int = testrepl$$$Lambda$10/[email protected] myNum(3) //> res0: Int = 6 myNum2(3) //> res1: Int = 6 myNum3(3) //> res2: Int = 6 def test1(x: Int): Int = { x * x } //> test1: (x: Int)Int def test2(x: Int) = x * x //> test2: (x: Int)Int (x: Int) => x * x //> res0: Int => Int = testrepl$$$Lambda$8/[email protected] val test3 = (x: Int) => x * x //> test3 : Int => Int = testrepl$$$Lambda$9/[email protected] val test4: Int => Int = (x) => x * x //> test4 : Int => Int = testrepl$$$Lambda$10/[email protected] test1(3) //> res1: Int = 9 test2(3) //> res2: Int = 9 test3(3) //> res3: Int = 9 test4(3) //> res4: Int = 9示例說明:
- 第1行:把匿名函式"(x: Int) => { x * 2 }"定義為一個值,賦值給myNum變數
- 第2行:省略myNum2的型別宣告“Int=>Int”,省略匿名函式的表示式花括號
- 第3行:省略x的型別宣告,省略匿名函式的表示式花括號
def test1(x: Int, y: Int): Int = { x + y } //> test1: (x: Int, y: Int)Int def test2(x: Int, y: Int) = x + y //> test2: (x: Int, y: Int)Int (x: Int, y: Int) => x + y //> res0: (Int, Int) => Int = testrepl$$$Lambda$8/[email protected] var test3 = (x: Int, y: Int) => x + y //> test3 : (Int, Int) => Int = testrepl$$$Lambda$9/[email protected] var test4: (Int, Int) => Int = (x, y) => { x + y } //> test4 : (Int, Int) => Int = testrepl$$$Lambda$10/[email protected] test1(2, 6) //> res1: Int = 8 test2(2, 6) //> res2: Int = 8 test3(2, 6) //> res3: Int = 8 test4(2, 6) //> res4: Int = 8
閉包
閉包是一個比較特殊的函式,反映了一個從開放到封閉的過程,返回值依賴於宣告在函式外部的一個或多個變數。
- 如果引用的變數是自由變數,沒有繫結具體的值,那麼此時這個函式是“開放的”。
- 如果引用的自由變數被繫結具體的值後,不再是“自由變數”,從而構成一個封閉的函式,那麼此時這個函式“被關閉”了。
var more = 1 //> more : Int = 1 val addMore = (x: Int) => x + more //> addMore : Int => Int = testrepl$$$Lambda$9/[email protected] addMore(10) //> res0: Int = 11 more = 9 addMore(10) //> res1: Int = 19示例說明: 函式定義“ val addMore = (x: Int) => x + more”中,引用了沒有在函式中定義的外部變數more,而more是一個自由變數,還沒有繫結具體的值,此時這個函式是“ 開放的”;而變數x是一個已在函式中明確定義的變數,只有在呼叫的時候才被賦值。 外部變數more確定具體值(“ var more = 1”)以後,那麼函式addMore中的more變數也就被繫結具體值了,不再是“自由變數”,此時這個函式是“關閉的”。 另外,每次addMore函式被呼叫時都會 建立一個新閉包。每個閉包都會訪問閉包建立時活躍的more變數。 閉包示例-2:
def plusStep(step: Int) = (num: Int) => num + step //> plusStep: (step: Int)Int => Int val myFunc = plusStep(3) //> myFunc : Int => Int = testrepl$$$Lambda$8/[email protected] println(myFunc(10)) //> 13示例說明: step是一個自由變數,它的值只有在執行的時候才能確定,num的型別是確定的,num的值只有在呼叫的時候才被賦值。 這樣的函式,被稱為“閉包”,它反映了一個從開放到封閉的過程。
高階函式
用函式作為形參或返回值的函式,稱為高階函式。 也就是說,高階函式就是一個接受其他函式作為引數或者返回一個函式的函式。 高階函式示例-1:def f(x: Int, y: Int) = x + y //> f: (x: Int, y: Int)Int def operate(f: (Int, Int) => Int) = { f(4, 4) } //> operate: (f: (Int, Int) => Int)Int operate(f)示例說明:函式operate是一個接受函式引數的函式,因此是一個高階函式。 高階函式示例-2:
//給定兩個數區間中的所有整數求和 def sumInts(a: Int, b: Int): Int = { if (a > b) 0 else a + sumInts(a + 1, b) } //> sumInts: (a: Int, b: Int)Int sumInts(1, 5) //> res0: Int = 15 //定義了一個新的函式sum,以函式f為引數 def sum(f: Int => Int, a: Int, b: Int): Int = { if (a > b) 0 else f(a) + sum(f, a + 1, b) } //> sum: (f: Int => Int, a: Int, b: Int)Int //定義了一個新的函式self,該函式的輸入是一個整數x,然後直接輸出x自身 def self(x: Int): Int = x //> self: (x: Int)Int //重新定義sumInts函式 def sumInts2(a: Int, b: Int): Int = sum(self, a, b) //> sumInts2: (a: Int, b: Int)Int sumInts2(1, 5) //> res1: Int = 15示例說明:函式sum的引數型別是(Int=>Int, Int, Int),結果型別是Int,也就是說函式sum是一個接受函式引數的高階函式。 高階函式示例-3:
def sum(f: Int => Int, a: Int, b: Int): Int = { if (a > b) 0 else f(a) + sum(f, a + 1, b) } //> sum: (f: Int => Int, a: Int, b: Int)Int def self(x: Int): Int = x //> self: (x: Int)Int def square(x: Int): Int = x * x //> square: (x: Int)Int def powerOfTwo(x: Int): Int = if (x == 0) 1 else 2 * powerOfTwo(x - 1) //> powerOfTwo: (x: Int)Int def sumInts(a: Int, b: Int): Int = sum(self, a, b) //> sumInts: (a: Int, b: Int)Int def sumSquared(a: Int, b: Int): Int = sum(square, a, b) //> sumSquared: (a: Int, b: Int)Int def sumPowersOfTwo(a: Int, b: Int): Int = sum(powerOfTwo, a, b) //> sumPowersOfTwo: (a: Int, b: Int)Int println(sumInts(1, 5)) //> 15 println(sumSquared(1, 5)) //> 55 println(sumPowersOfTwo(1, 5)) //> 62示例說明:
- sumInts函式:求連續整數的和
- sumSquared函式:求連續整數的平方和
- sumPowersOfTwo函式:求連續整數的關於2的冪次和
佔位符語法
使用下劃線作為一個或多個引數的佔位符,只要每個引數在函式字面量內僅出現一次。println("Testing, Scala!") //> Testing, Scala! val numList = List(-3, -5, 1, 6, 9) //> numList : List[Int] = List(-3, -5, 1, 6, 9) numList.filter(x => x > 0) //> res0: List[Int] = List(1, 6, 9) numList.filter(_ > 0) //> res1: List[Int] = List(1, 6, 9)示例說明: 當採用下劃線的表示方法時,對於列表numList中的每個元素,都會依次傳入用來替換下劃線。 比如,首先傳入-3,判斷-3>0是否成立,是則把該值放入結果集合,否則捨棄;然後傳入-5,判斷-5>0是否成立,依此類推。
柯里化
柯里化函式(Curried Funciton)把具有多個引數的函式轉化為一條函式鏈,每個節點上是單一引數。
在函數語言程式設計中,可以基於一些通用性的函式,利用柯里化函式等來構造新函式,而不需要重新定義新函式。
示例:
def add(x: Int, y: Int) = x + y //> add: (x: Int, y: Int)Int add(1, 2) //> res0: Int = 3 def addCurried(x: Int)(y: Int) = x + y //> addCurried: (x: Int)(y: Int)Int addCurried(1)(2) //> res1: Int = 3 val addOne = addCurried(1)_ //> addOne : Int => Int = TestScala$$$Lambda$8/[email protected] addOne(2) //> res2: Int = 3示例說明: 函式add和addCurried的函式定義時等價的。 “addCurried(1)_”的下劃線是通配後面所有的引數列表。
遞迴
在函數語言程式設計中利用遞迴函式(Recursive Funtion)實現迴圈。def factorial(n: Int): Int = if (n <= 0) 1 else n * factorial(n - 1) //> factorial: (n: Int)Int factorial(5) //> res0: Int = 120
尾遞迴
在尾遞迴函式(Tail Recursive Funtion)中所有遞迴形式的呼叫都出現在函式的末尾。 當編譯器檢測到一個函式呼叫時尾遞迴的時候,它就覆蓋當前的活動記錄,而不是在棧中去建立一個新的。 Scala編譯器不會主動進行尾遞迴優化,需要“@annotation.tailrec”來 告知Scala編譯器package testscala object TestScala { def main(args: Array[String]) { println("Testing, Scala!") val res = factorial2(5, 1) println(res) } @annotation.tailrec def factorial2(n: Int, m: Int): Int = if (n <= 0) m else factorial2(n - 1, m * n) }
示例:求整數a到b的相加之和
def sum(f: Int => Int)(a: Int)(b: Int): Int = { @annotation.tailrec def loop(n: Int)(acc: Int): Int = { if (n > b) { println(s"n=${n},acc=${acc}") acc } else { println(s"n=${n},acc=${acc}") loop(n + 1)(acc + f(n)) } } loop(a)(0) } //> sum: (f: Int => Int)(a: Int)(b: Int)Int sum(x => x)(1)(5) //> n=1,acc=0 //| n=2,acc=1 //| n=3,acc=3 //| n=4,acc=6 //| n=5,acc=10 //| n=6,acc=15 //| res0: Int = 15 sum(x => x * x)(1)(5) //> n=1,acc=0 //| n=2,acc=1 //| n=3,acc=5 //| n=4,acc=14 //| n=5,acc=30 //| n=6,acc=55 //| res1: Int = 55 sum(x => x * x * x)(1)(5) //> n=1,acc=0 //| n=2,acc=1 //| n=3,acc=9 //| n=4,acc=36 //| n=5,acc=100 //| n=6,acc=225 //| res2: Int = 225 val square = sum(x => x * x)_ //> square : Int => (Int => Int) = TestScala$$$Lambda$13/[email protected] square(1)(5) //> n=1,acc=0 //| n=2,acc=1 //| n=3,acc=5 //| n=4,acc=14 //| n=5,acc=30 //| n=6,acc=55 //| res3: Int = 55