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