柯里化對函數式編程有何意義?

我看過 Haskell 的 Curry 函數, 原以為是函數是編程普通的功能

但回頭看 Scheme 卻發現圓括弧對柯里化支持並不是很好

具體說不上, 找到有個看不太懂的鏈接

http://stackoverflow.com/questions/6487104/curry-in-scheme

http://www.engr.uconn.edu/%7Ejeffm/Papers/curry.html

柯里化對函數式編程來說必要麼?

我的本意是, 自動給 lisp 加括弧, 發現柯里化有問題

https://gist.github.com/1615885


並非「科里化」對函數式編程有意義。而是,函數式編程在把函數當作一等公民的同時,就不可避免的會產生「科里化」這種用法。所以它並不是因為「有什麼意義」才出現的。當然既然存在了,我們自然可以探討一下怎麼利用這種現象。

詳見如何理解functional programming里的currying與partial application? - 羅宸的回答


Currying 的重要意義在於可以把函數完全變成「接受一個參數;返回一個值」的固定形式,這樣對於討論和優化會更加方便。


Given that we have only single parameter functions, and we』re in a language with first-class functions, how can we implement multi-parameter functions?

Currying is a way of implementing multi-parameter functions.

from:Currying vs. Partial Application


那些說lisp不能支持柯里化的也是夠了,我隨手寫了一個(起名困難晚期求輕噴)

(因為手機發答案總會把行開頭的空格吃掉,也發不了截屏,所以只能手寫了,字不好見諒)

我不知道「圓括弧對柯里化不友好」的說法是哪兒來的,柯里化cons 1寫作(c cons 1),括弧一個不多,而且沒人會

(c(c cons 1)nil)吧………………

順便一說,柯里化絕對是從邏輯學家那裡直接弄來的那些東西之中少有的毒瘤

1.一旦語言直接支持柯里化,那麼變長參數和默認參數都不能用了

2沒有任何理由可以說明,為什麼可以用f x表示f接收第一個參數後返回的函數,卻沒有一種方式來表示函數接收第二個參數後返回的函數

3.在lambda演算里柯里化是沒有問題的,因為一切都是閉包,但是,在計算機里,#"cons用函數指針就可以表示,不是閉包,(cons 1 nil)是值(1)也不是閉包,而cons 1卻是閉包,沒有理由不加區別地使用

cons,cons 1,cons 1 nil

4考慮一個函數f:(a-&>b-&>(c-&>d))-&>a-&>b-&>(c-&>d)

假如在不支持柯里化的語言里,你寫a=f f1 1 2,本來想寫全三個參數,手一抖漏了一個,就報錯了,但是在支持柯里化的語言里,它以為你在柯里化,毫無反應,然後接下來又是一函數,g,(a-&>b)-&>a-&>M b,這回你想柯里化了,寫g a,它還是沒反應,如此這般到了最後報錯的時候,就跟野指針似的,不知道錯到哪裡去了

就這麼一東西,只是在haskell里為了用hm類型論而無奈添加的,竟然被某些人吹捧為函數式編程的基礎,並且還覺得高那些不支持柯里化的語言一頭,我真不知道他們怎麼想的


參考Scala高階函數的語法糖

// 沒使用語法糖的 sum 函數
def sum(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
}
// 使用語法糖後的 sum 函數
def sum(f: Int =&> Int)(a: Int, b: Int): Int =
if (a &> b) 0 else f(a) + sum(f)(a + 1, b)

把原來的 sum函數轉化成這樣的形式,好處在哪裡?答案是我們獲得了更多的可能性,比如剛開始求和的上下限還沒確定,我們可以在程序中把一個函數傳給 sum,sum(fact)完全是一個合法的表達式,待後續上下限確定下來時,再把另外兩個參數傳進來。對於 sum 函數,我們還可以更進一步,把 a,b 參數再轉化一下,這樣 sum 函數就變成了這樣一個函數:它每次只能接收一個參數,然後返回另一個接收一個參數的函數,調用後,又返回一個只接收一個參數的函數。這就是傳說中的柯里化,多麼完美的形式!在現實世界中,的確有這樣一門函數式編程語言,那就是 Haskell,在 Haskell 中,所有的函數都是柯里化的,即所有的函數只接收一個參數!


"自動給LISP加括弧" 這事如果你想做的話, 推薦使用的方式是類似python和F#那樣, 用縮進來表示級別. 然後由編譯器來完成 tab 和括弧的一一對應轉換. (我假設你的自動加括弧是你自己打算設計一門新的沒括弧的lisp方言)

如果你的本意是做個IDE自動給現有的scheme或者common lisp 之類加括弧, 這是難以實現的, 因為宏的存在讓括弧幾乎可以毫無規律可言, 相同輸入可以有完全不同的多種輸出, 也就是 "刪除括弧, 重新加入括弧" 這件事不可逆了.

上面是跑題----------------------------------下面才是問題回答

科里化理論上非必須, 實際上是必須的. 科里化本質是用單參數的函數拼出多參數的函數, 這樣拼出來的函數有個特點就是只接受部分參數時是另外一個函數. 這對函數式語言來說太重要了, 可以簡化一大堆東西. 當然,如果所有函數式語言都不支持科里化,其實世界照轉, 只是你需要在自己代碼裡面變相地不完全的帶著bug地重複地實現科里化...


如果參數只允許用函數,比如過濾條件的一些函數。

那麼如果你想讓這個過濾條件更加靈活,也就是可以傳參數給它,也就是說,帶參數的函數

實現方法有兩種,一種是返回值是函數,還有一種就是科里化。這樣會比返回值是函數更加清晰。


函數curry之後,可以只提供一個參數,其他的參數作為這個函數的「環境」來創建。這就能讓函數回歸到原始的:一個參數進去一個值出來的狀態。


我的經驗中柯里化有兩個好處:

01 可以方便地用一般化函數來造特殊函數

比如用foldl函數來造求和函數和求積函數

sum = foldl (+) 0

product = foldl (*) 1

02 更加自然地處理參數。比如applicative裡面參數帶了上下文,還有printf有不確定數目的參數,都用柯里化簡化了處理

最後,柯里化有一個缺點,就是在極少數情況下會讓程序員搞不清參數的數目


函數式語言的currying特性來自於lambda calculus,lambda calculus只支持單參函數,但它可以返回一個函數來接受第二個參數。所以scheme也支持這個特性。

我猜題主是想要這個語法糖

val f x y z = x+y+z,而這個等價於

val f = fn x =&> fn y =&> fn z =&> x+y+z


柯里化在做函數組合 compose 時非常有用,讓人們將關注的重點聚焦到函數本身,而不因冗餘的數據參數分散注意力。

能夠寫出 Pointfree 風格的程序,讓代碼更簡潔。

可以看下這兩篇文章:

Thinking in Ramda: 部分應用

Thinking in Ramda: 無參數風格編程 (Pointfree Style)


感覺簡化了某些寫法

scala舉例:

def until( condition: =&> Boolean): ( =&> Unit) =&> Unit = {

def run(block: =&> Unit): Unit = {

if (!condition){

block

until(condition)(block)

}

}

run _

}

柯里化的:

def until(condition: =&> Boolean)(block: =&> Unit): Unit = {

if(! condition){

block

until(condition)(block)

}

}

一下就清爽許多了

多參數也行,但是調用的時候不美觀:until(condition,{...})

手機就不敲了


個人觀點:

比如編譯器,要編譯1+1,是因為知道底層有add指令,可以將1+1編譯為類似於

add ax,1

add ax,1

或者

add ax,1,1

但是如果是1+2+3,底層是不會有add ax,1,2,3,不會有add ax,1,2,3,4這樣的東西的

底層的東西必然是基本的,原始的,自足的,不能無限描述的,「元」的

所以編譯器要將1+2+3分解給底層的「元」add指令

所以數學會有什麼皮亞諾公里解釋自然數,定義基本操作,而不是直接給無限的描述

對於計算機的理論,是不會假設底層有真機和彙編指令的,所假設的底層是lambda函數和lambda運算體系。

對於1+2+3這樣的運算,同樣需要分解成基本的運算,而且1+1,1+2+3,1+2+3+4都要能分解為基本的運算和邏輯

有了科里化,就能很好的描述這個無限和擴張的過程,良好的處理了多參函數的問題,不會導致add ax,1,2,3....這樣的東西,「元」已經確定了,就是單參函數加上函數做參數的機制,其實就是lambda演算那套東西,所以科里化意義我覺得很大。不一定是從實用角度,而是從理論意義角度

程序語言其實就是語法便宜,就像下圍棋總要先佔邊角,因為那裡有便宜。就算是宇宙流和吳清源的「星,三三,天元」,也要先點角,不會上來就天元。

有有名變數,函數,遞歸之類的c語言一樣的高級語言,佔了一個角,給了人很多便宜

面向對象佔了一個角

lisp的語法便宜佔了一個角

科里化應該也佔了一個角,這個角價值應該很大


你寫 scheme 的話,下面這樣是很噁心的。

(map (lambda (x) (* 2 x)) "(1 2 3))

看人家有自動柯里化的就,簡單了

map (2 *) [1,2,3]

所以,以scheme的觀點來看,就是少寫多少lambda呀。

宏不是號稱讓程序寫程序嗎!

你看人家這個才是真正的讓程序寫程序呢。


我覺得,先不說必要與否,柯里化確實在很多時候簡化了函數式編程的複雜性,使編程更加優雅,就像牛奶對於我們並不是必須的,但其銷量是毋庸置疑的。

函數柯里化(currying)又可叫部分求值。一個currying的函數接收一些參數,接收了這些參數之後,該函數並不是立即求值,而是繼續返回另一個函數,剛才傳入的參數在函數形成的閉包中被保存起來,待到函數真正需要求值的時候,之前傳入的所有參數都能用於求值。


推薦閱讀:

C++、Haskell、Scala 和 Rust 究竟哪個最複雜?

TAG:編程語言 | 函數式編程 | Haskell | Scheme |