函數式中的 currying

currying 是函數式語言中常常遇到的一個概念,翻譯成 柯里化,不是庫裏化。app

currying 指的是將接收多個參數的函數變換成接收一個單一參數,而且返回接收餘下的參數並且返回結果的新函數的技術。函數

提及來比較拗口,直接看下面的代碼。scala

def add(x: Int, y: Int): Int = x + y

    //call add
    add(1, 2)
    add(3, 4)

可是若是咱們使用 currying 的寫法,那麼能夠將兩個參數分開,接收第一個參數(x),而後返回一個函數,這個函數以第二個參數(y)爲參數,返回結果:翻譯

def add(x:Int) => (y: Int) = x + y

而後咱們就能夠這樣調用 add 了。code

add(1)(2)
    add(3)(4)

scala 對於這種寫法提供了語法糖,add 的 currying 寫法咱們能夠更簡單地寫爲:blog

def add(x: Int)(y: Int): Int = x + y

嘗試將一個函數(這裏指f)寫成 curry 方式:get

def curry[A, B, C](f: (A, B) => C): A => (B => C) = a => b => f(a, b)

這裏的 currying 函數,它接收兩個參數,分別爲 A 類型和 B 類型,而後返回一個 C 類型的結果。那麼咱們能夠經過獲得一個偏函數將其轉化爲 curry 的方式,即 A => (B => C)。具體流程是,用第一個參數 a ,獲得一個函數,這個函數使用 b 做爲參數,而後獲得結果。這裏中間的這個函數,是一個偏函數。
一樣咱們也能夠寫一個 uncurry 的方法(即將 currying 的函數f轉化成非 currying 的):it

def uncurry[A, B, C](f: A => B => C): (A, B) => C = 
        (a, b) => f(a)(b)

這裏在 currying 的過程當中,使用了偏函數(partial applied function),偏函數是什麼?這個其實更好理解一些,通俗的講,能夠理解爲定製化函數,好比咱們有一個函數須要兩個參數,那麼若是咱們返回固定其中一個參數的函數,那麼咱們就獲得了一個偏函數:io

def add(x: Int, y: Int): Int = x + y
    def add1(y: Int): Int = add(1, y)

這裏咱們就是固定了 add 函數中的 x 參數,而後返回了另一個函數 add1,如今 add1 只有一個參數,咱們直接能夠調用 add1:function

add1(5) //6

能夠看到,currying 和偏函數的主要區別就是,是否固定了參數,偏函數一旦固定參數以後,就返回了新的函數。而 currying 通常並無固定參數,只是相似於使用了黑魔法,讓多個參數的函數,最後變成了一個函數鏈。

其實在 C 語言中也有偏函數這種用法:

int foo(int a, int b, int c) {
    return a + b + c;
}

int foo23(int a, int c) {
  return foo(a, 23, c);
}

可是在函數式語言中,偏函數以及 currying 能發揮更大的做用,緣由是,函數式中,咱們的參數還能夠是函數。

def sum(f: Int => Int, a: Int, b: Int): Int = 
    if(a > b) 0
    else f(a) + sum(a+1, b)

這裏有一個 sum 函數,它將 a 和 b 之間的全部的數,用 f 計算以後相加,好比:

sum(x => x * x, 1, 10)

那麼就是計算 1 到 10 之間全部數的平方和。可是若是咱們要計算全部數的立方和呢,四次方呢?

當然咱們能夠將 f 傳遞成不一樣的值,可是咱們也能夠更簡單地直接獲得定製化以後的函數,即用不一樣的 f 獲得不一樣的函數。

爲了達到咱們的目的,咱們將函數寫成 currying 形式:

def sum(f: Int => Int)(a: Int, b: Int): Int =
    if(a >b) 0 else f(a) + sum(f)(a+1, b)

這個時候 sum 函數的類型是什麼呢?是:(Int => Int) => (( Int, Int) => Int)。第一層函數是一個參數類型爲 Int => Int 的函數,其返回是一個函數,這個函數以 (Int, Int) => Int 類型爲返回值。所以,若是咱們傳遞不一樣的第一層函數的參數,即 Int => Int,那麼就將獲得不一樣的第二層函數,即類型爲 (Int, Int) => Int 的函數。

所以,咱們能夠傳入不一樣的 f,傳入 f 以後,獲得的函數就是以 a 和 b 爲參數的函數:

爲此咱們將 sumT 函數定義成以下形式,使得 sumT 函數返回一個類型爲 (Int, Int) => Int 的函數:

def sumT(f: Int => Int): (Int, Int) => Int = {
    def sumF(a: Int, b: Int): Int =
        if(a > b) 0
        else f(a) + sumF(a+1, b)
    sumF
}

而後咱們就能夠傳遞不一樣的 f,獲取不一樣的偏函數:

def sum3 = sum(x => x * x * x)  //三次方
def sum4 = sum(x => x * x * x * x)  //四次方

這裏能夠看出,偏函數和 currying 是有很大的關聯的。函數 currying 後,很容易實現某些偏函數。

在 scala 中,若是須要減小函數的參數,即固定函數的某些參數,那麼使用偏函數便可。那麼,currying的做用是什麼?

做用有二,一個是能夠將函數的參數寫成{}形式,這對於參數是函數時可讀性更好。

在currying以前,可能須要這樣寫:

using (new File(name), f => {......})

在有了 currying 以後,咱們能夠寫成更易讀的方式:

using(new File(name)){ f => ......}

第二個做用就是用於類型推導:

通常的 currying 能夠寫成

def f(arg1)(arg2)......(argn) => E
//等同於
def f(arg1)(arg2)......(argn-1) => { def g(argn) => E; g}
/.更簡單點
def f(arg1)(arg2)......(argn-1) => (argsn => E) 
//繼續最後就能夠寫成
def f = args1 => args2......=>argsn => E

這樣寫以後,scala 能夠對參數之間的類型進行推導:左邊參數的類型能夠用於推導右邊參數的類型,從而某些參數就能夠簡化寫法:

def dropWhile[A](as: List[A], f: A => Boolean): List[A] = ......
//調用
dropWhile(List(1,2,3,4), (x: Int => x < 4))

//currying以後
dropWhile[A](as:List[A])(f: A => Boolean): List[A] = ......
//調用
dropWhile(List(1,2,3,4))(x => x < 4)  //這裏再也不須要指明 x 的類型

這樣就能夠用來結合匿名函數的下劃線寫出更加簡潔的代碼。

currying 也能夠在某些只容許使用單個參數的函數的情景下發揮做用,利用 currying 使用層層嵌套的單參數函數,能夠實現語法層面的多參數函數。


[1]: http://stackoverflow.com/questions/8063325/usefulness-as-in-practical-applications-of-currying-v-s-partial-application-i
[2]: http://www.vaikan.com/currying-partial-application/
[3]: http://www.codecommit.com/blog/scala/function-currying-in-scala
[4]: https://book.douban.com/subject/26772149/
[5]: https://www.coursera.org/learn/progfun1/lecture/fOuQ9/lecture-2-2-currying

相關文章
相關標籤/搜索