函數加里化和偏函數應用的比較
【名詞解釋】Currying:因為是美國數理邏輯學家哈斯凱爾·加里(Haskell Curry)發明了這種函數使用技巧,所以這樣用法就以他的名字命名為Currying,中文翻譯為“加里化”。
我感覺很多人都對函數加里化(Currying)和偏函數應用(Partial Application)之間的區別搞不清楚,尤其是在相似的上下文環境中它們同時出現的時候。
偏函數解決這樣的問題:如果我們有函數是多個參數的,我們希望能固定其中某幾個參數的值。
幾乎所有編程語言中都有非常明顯的偏函數應用。在C語言中:
- int foo(int a, int b, int c) {
- return a + b + c;
- }
- int foo23(int a, int c) {
- return foo(a, 23, c);
- }
foo23
函數實際上就是一個foo
函數的偏函數應用,參數b
的值被固定為23。
當然,像這樣明顯的偏函數并沒有太大的用處;我們通常會希望編程語言能提供我們某些偏函數特征。
例如,在Python語言中,我們可以這樣做:
- from functools import partial
- def foo(a,b,c):
- return a + b + c
- foo23 = partial(foo, b=23)
- foo23(a = 1, c = 3) # => 27
函數加里化(Currying)明顯解決的是一個完全不同的問題:如果我們有幾個單參數函數,并且這是一種支持一等函數(first-class)的語言,如何去實現一個多參數函數?函數加里化是一種實現多參數函數的方法。
下面是一個單參數的Javascript函數:
- var foo = function(a) {
- return a * a;
- }
如果我們受限只能寫單參數函數,可以像下面這樣模擬出一個多參數函數:
- var foo = function(a) {
- return function(b) {
- return a * a + b * b;
- }
- }
通過這樣調用它:(foo(3))(4)
,或直接 foo(3)(4)
。
注意,函數加里化提供了一種非常自然的方式來實現某些偏函數應用。如果你希望函數foo
的***個參數值被固定成5,你需要做的就是var foo5 = foo(5)
。這就OK了。函數foo5
就是foo
函數的偏函數。注意,盡管如此,我們沒有很簡單的方法對foo
函數的第二個參數偏函數化(除非先偏函數化***個參數)。
當然,Javascript是支持多參數函數的:
- var bar = function(a, b) {
- return a * a + b * b;
- }
我們定義的bar
函數并不是一個加里化的函數。調用bar(5)
并不會返回一個可以輸入12的函數。我們只能像bar(5,12)
這樣調用這個函數。
在一些其它語言里,比如 Haskell 和 OCaml,所有的多參數函數都是通過加里化實現的。
下面是一個把上面的foo
函數用OCaml語言寫成的例子:
- let foo = fun a ->
- fun b ->
- a * a + b * b
下面是把上面的bar
函數用OCaml語言寫成的例子:
- let bar = fun a b ->
- a * a + b * b
頭一個函數我們叫做“顯式加里化”,第二個叫做“隱式加里化”。
跟Javascript不一樣,在OCaml語言里,foo
函數和bar
函數是完全一樣的。我們用完全一樣的方式調用它們。
- # foo 3 4;;
- - : int = 25
- # bar 3 4;;
- - : int = 25
兩個函數都能夠通過提供一個參數值來創造一個偏函數:
- # let foo5 = foo 5;;
- val foo5 : int -> int = <fun>
- # let bar5 = bar 5;;
- val bar5 : int -> int = <fun>
- # foo5 12;;
- - : int = 169
- # bar5 12;;
- - : int = 169
事實上,我們可以把下面這個匿名函數:
- fun arg1 arg2 ... argN -> exp
當作是下面這個函數的簡寫:
- fun arg1 -> fun arg2 -> ... -> fun argN -> exp
函數加里化和偏函數應用的總結
- 偏函數應用是找一個函數,固定其中的幾個參數值,從而得到一個新的函數。
- 函數加里化是一種使用匿名單參數函數來實現多參數函數的方法。
- 函數加里化能夠讓你輕松的實現某些偏函數應用。
- 有些語言(例如 Haskell, OCaml)所有的多參函數都是在內部通過函數加里化實現的。