1. 程式人生 > >Scala - 08 - 函數語言程式設計:高階函式

Scala - 08 - 函數語言程式設計:高階函式

函數語言程式設計的崛起

函數語言程式設計中的 “值不可變性”避免了對公共的可變狀態進行同步訪問控制的複雜問題,能夠較好滿足分散式並行程式設計的需求,適應大資料時代的到來。

函式是第一等公民

  • 可以作為實參傳遞給另外一個函式
  • 可以作為返回值
  • 可以賦值給變數
  • 可以儲存在資料結構裡
  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/
[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)
示例說明: 第7行把函式"(x: Int, y: Int) => x + y"作為一個值(函式字面量)賦給test6變數。 由此可見,Scala中的函式和普通變數的使用方式完全一致。

匿名函式

使用匿名函式(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的型別宣告,省略匿名函式的表示式花括號
注意:型別宣告“Int=>Int”和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

閉包

閉包是一個比較特殊的函式,反映了一個從開放到封閉的過程,返回值依賴於宣告在函式外部的一個或多個變數。
  • 如果引用的變數是自由變數,沒有繫結具體的值,那麼此時這個函式是“開放的”。
  • 如果引用的自由變數被繫結具體的值後,不再是“自由變數”,從而構成一個封閉的函式,那麼此時這個函式“被關閉”了。
函式引用的外部變數,必須在函式外部給出值。 閉包示例-1:
  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