Scala基礎 - 柯里化(Currying)及其應用

1. 介紹

柯里化(currying, 以邏輯學家Haskell Brooks Curry的名字命名)指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個以原有第二個參數做爲參數的函數。 在Scala中方法和函數有細微的差異,一般編譯器會自動完成方法到函數的轉換。若是想了解Scala方法和函數的具體區別,請參考博文Scala基礎 - 函數和方法的區別java

2. Scala中柯里化的形式

Scala中柯里化方法的定義形式和普通方法相似,區別在於柯里化方法擁有多組參數列表,每組參數用圓括號括起來,例如:app

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

multiply方法擁有兩組參數,分別是(x: Int)和(y: Int)。
multiply方法對應的柯里化函數類型是:ide

Int => Int => Int

柯里化函數的類型聲明是右結合的,即上面的類型等價於:函數

Int => (Int => Int)

代表該函數若只接受一個Int參數,則返回一個Int => Int類型的函數,這也和柯里化的過程相吻合。.net

3. 探究柯里化函數

咱們仍以上面定義的multiply方法爲例探索柯里化的一些細節:code

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

上面的代碼定義了一個柯里化方法,在Scala中能夠直接操縱函數,可是不能直接操縱方法,因此在使用柯里化方法前,須要將其轉換成柯里化函數。最簡單的方式是使用編譯器提供的語法糖:blog

val f = multiply _

返回的函數類型是:ip

Int => Int => Int

使用Scala中的部分應用函數(partially applied function)技巧也能夠實現轉換,可是請注意轉後後獲得的並非柯里化函數,而是一個接受兩個(而不是兩組)參數的普通函數:資源

val f = multiply(_: Int)(_: Int)

轉後後獲得的類型爲:get

(Int, Int) => Int

其實就是一個接受兩個Int型參數的普通函數類型。

先傳入第1個參數:

val f1 = f(1)

返回類型爲:

Int => Int

即一個接受一個Int參數返回Int類型的函數。 繼續傳入第2個參數:

val f2 = f1(2)

返回類型爲:

Int

兩組參數都已經傳入,返回一個Int類型結果。

4. 柯里化(currying)函數和部分應用函數(partial applied)的區別

下面代碼定義一個普通方法multiply1和一個currying方法multiply2,並將其轉換相應的函數類型:

def multiply1(x: Int, y:Int, z:Int) = x * y * z
val partialAppliedMultiply = multiply1 _
//類型:(x: Int, y: Int, z: Int) => Int

def multiply2(x: Int)(y: Int)(z: Int) = x * y * z
val curryingMultiply = multiply2 _
//類型:Int => (Int => (Int => Int))

在調用時,curryingMultiply能夠依次傳入各個參數,而partialAppliedMultiply在傳入部分參數時,必須顯示指定剩餘參數的佔位符:

val curryingMultiply1 = curryingMultiply(1)
//類型:Int => (Int => Int)

val partialAppliedMultiply1 = partialAppliedMultiply(1, _:Int, _: Int)
//類型:(Int, Int) => Int

另外,curryingMultiply1的類型仍然是currying類型,而partialAppliedMultiply1的類型仍然是普通函數類型。

5. 應用:控制抽象(Control Abstraction)

5.1 控制抽象介紹

對於一些通用的操做能夠實現成控制抽象,例如像文件打開、關閉操做。實現成控制抽象的好處是,能夠在使用的時候,看起來更像是語言級別提供的功能。

5.2 抽象控制的實現基礎

5.2.1 無參函數

無參函數的類型是() => T,在使用時爲了簡化能夠省略(),例如:

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

這樣定義以後,在使用的時候就能夠省略() =>,

runInThread{
    println("Hi")
  }

5.2.2 使用{}替代()

若是方法只有一個參數,則可使用{}替代(),例如:

runInThread{
    println("Hi")
  }

5.2.3 傳名參數(by-name parameter)

與傳名參數相對的是傳值參數。傳值參數在函數調用以前表達式會被求值,例如Int,Long等數值參數類型;傳名參數在函數調用前表達式不會被求值,而是會被包裹成一個匿名函數做爲函數參數傳遞下去,例如高階函數的函數參數就是傳名參數。

5.3 控制抽象示例

withPrintWriter是一個柯里化方法,它接受兩組參數,第1組參數是待操做的文件資源,第2組參數是操做文件資源的函數:

def withPrintWriter(file: File)(op: PrintWriter => Unit) {
    val writer = new PrintWriter(file)
    try {
      op(writer)
    } finally {
      writer.close()
    }
}

用法以下:

withPrintWriter(new File("date.txt")) {
    writer => writer.println(new java.util.Date)
}

withPrintWriter確保文件資源在被使用以後必定會被關閉,而且在使用的時候,看起來更像是語言內置的關鍵字函數。

6. 參考

  1. Programming in Scala, 2nd Edition
  2. 快學Scala
相關文章
相關標籤/搜索