1. 程式人生 > >Scala中的函數語言程式設計

Scala中的函數語言程式設計

Scala中的函數語言程式設計

作為一門面向物件與函式式的混合正規化語言,Scala 並不強制要求函式必須是純函式,也不要求變數不可變。儘管它的確推薦你在任何可能的情況下這麼做。

以下是幾個高階函式,我們將其組合在一起,用它來對一個整數列表進行遍歷,過濾出其中的偶數,對每個偶數乘以2,再使用reduce 函式將各個整數乘在一起:

object Test extends App {
  val l = (1 to 10) filter (_ % 2 == 0) map (_ * 2) reduce (_ * _)
  println(l)
}

輸出:
122880

_ % 2 == 0,_ * 2,與_ * _ 是函式字面量。前兩個函式只有一個引數,賦值給佔位符_;最後一個函式帶兩個引數,該函式本身是reduce 函式的引數。

reduce 函式將各個元素做累乘,也就是說它將整數的集合reduce 為一個值。reduce 函式帶兩個引數,均賦值給了佔位符_。其中一個引數是集合中的當前元素,另一個引數就是累乘值,是上一次呼叫reduce函式得到的部分元素的累乘結果。(第一個引數是累乘引數,還是第二個引數是累乘引數取決於具體實現)對傳入的函式的要求是:其計算必須滿足結合律,類似乘法與加法,因為我們不保證集合中元素的計算順序。於是,我們只用了一行程式碼,沒有用可變的計數器,也沒有用可變變數作為累乘結果,就“迴圈”遍歷了列表得出結果。

把程式碼稍微修改一下:

object Test extends App {
  var factor = 2
  val multiplier = (i: Int) => i * factor
  var l = (1 to 10) filter (_ % 2 == 0) map multiplier reduce (_ * _)
  println(l)
  factor = 3
  l = (1 to 10) filter (_ % 2 == 0) map multiplier reduce (_ * _)
  println(l)
}

輸出:
122880
933120

我們定義一個名為factor 的變數,作為累乘因子。而之前的匿名函式_ * 2 則替換為一個名為multiplier 的變數,變數的值由factor 決定。注意,multiplier 事實上也是一個函式。由於函式在Scala 中是第一等的,因此我們定義了表示函式的變數。不過,這不是簡單的替換,在這裡multiplier 引用了factor,而不是將其硬編碼為2。注意看我們使用兩個不同的factor 值時,程式的執行結果。首先我們的輸出值為122880,與之前相同,但接著輸出值為933120。

儘管multiplier 是一個不可變的函式字面量,當factor 改變時,multiplier 的行為也跟著改變。在multiplier 函式中有兩個變數i 和factor。i 是一個函式的引數,所以每次呼叫時,i都綁定了一個新的值。然而,factor 並不是multiplier 的引數,而是一個自由變數,是一個當前作用域中某個值的引用。所以,編譯器建立了一個閉包,用於包含(或“覆蓋”)multiplier與它引用的外部變數的上下文資訊,從而也就綁定了外部變數本身。這就是factor 變化時,multiplier 也跟著變化的原因。Multiplier 引用了factor,每次呼叫時都重新讀取factor 的值。如果函式沒有外部引用,那它就只是包含了自身,不需要外部上下文資訊。

即使factor 處於某個區域性作用域(如某個方法)中,而我們將multiplier 傳遞給其他作用域(如另一個方法)中時,這一機制仍然有效。該自由變數factor 的有效性一直伴隨multiplier 函式:

object Test extends App {
  def m1(multiplier: Int => Int) = {
    (1 to 10) filter (_ % 2 == 0) map multiplier reduce (_ * _)
  }

  def m2: Int => Int = {
    val factor = 2
    val multiplier = (i: Int) => i * factor
    multiplier
  }

  println(m1(m2))
}

輸出:
122880

我們呼叫m2, 返回了一個型別為Int => Int 的函式。返回的內部值是multiplier,multiplier 引用了m2 中定義的變數factor,一旦m2 返回,就離開了factor 變數的作用域。然後呼叫m1,將m2 的返回值傳遞給它。儘管factor 變數已經離開了m1 的作用域,但程式的輸出與之前的例子相同,仍為122880。m2 返回的函式事實上是一個閉包,它包含了對factor 的引用。

三個關鍵字

• 函式
一種具有名或匿名的操作。其程式碼直到被呼叫時才執行。在函式的定義中,可能有也可能沒有引用外部的未繫結變數。
• Lambda
一種匿名函式。在它的定義中,可能有也可能沒有引用外部的未繫結變數。
• 閉包
是一個函式,可能匿名或具有名稱,在定義中包含了自由變數,函式中包含了環境資訊,以繫結其引用的自由變數。