函數(shù)式編程初探

http://www.ruanyifeng.com/blog/2012/04/functional_programming.html

誕生50多年之后,函數(shù)式編程(functional programming)開始獲得越來越多的關注鲸阻。
不僅最古老的函數(shù)式語言Lisp重獲青春跋涣,而且新的函數(shù)式語言層出不窮缨睡,比如Erlang、clojure陈辱、Scala换淆、F#等等掀淘。目前最當紅的Python绿贞、Ruby废登、Javascript,對函數(shù)式編程的支持都很強利赋,就連老牌的面向?qū)ο蟮腏ava水评、面向過程的PHP,都忙不迭地加入對匿名函數(shù)的支持媚送。越來越多的跡象表明中燥,函數(shù)式編程已經(jīng)不再是學術界的最愛,開始大踏步地在業(yè)界投入實用塘偎。
也許繼"面向?qū)ο缶幊?之后疗涉,"函數(shù)式編程"會成為下一個編程的主流范式(paradigm)。未來的程序員恐怕或多或少都必須懂一點吟秩。


但是咱扣,"函數(shù)式編程"看上去比較難,缺乏通俗的入門教程涵防,各種介紹文章都充斥著數(shù)學符號和專用術語闹伪,讓人讀了如墜云霧。就連最基本的問題"什么是函數(shù)式編程"壮池,網(wǎng)上都搜不到易懂的回答偏瓤。
下面是我的"函數(shù)式編程"學習筆記,分享出來火窒,與大家一起探討硼补。內(nèi)容不涉及數(shù)學(我也不懂Lambda Calculus)驮肉,也不涉及高級特性(比如lazy evaluationcurrying)熏矿,只求盡量簡單通俗地整理和表達,我現(xiàn)在所理解的"函數(shù)式編程"以及它的意義离钝。
我主要參考了Slava Akhmechet的"Functional Programming For The Rest of Us"票编。
一、定義
簡單說卵渴,"函數(shù)式編程"是一種"編程范式"(programming paradigm)慧域,也就是如何編寫程序的方法論。
它屬于"結構化編程"的一種浪读,主要思想是把運算過程盡量寫成一系列嵌套的函數(shù)調(diào)用昔榴。舉例來說辛藻,現(xiàn)在有這樣一個數(shù)學表達式:

  (1 + 2) * 3 - 4

傳統(tǒng)的過程式編程,可能這樣寫:

  var a = 1 + 2;
  var b = a * 3;
  var c = b - 4;

函數(shù)式編程要求使用函數(shù)互订,我們可以把運算過程定義為不同的函數(shù)吱肌,然后寫成下面這樣:

  var result = subtract(multiply(add(1,2), 3), 4);

這就是函數(shù)式編程。
二仰禽、特點
函數(shù)式編程具有五個鮮明的特點氮墨。
1. 函數(shù)是"第一等公民"
所謂"第一等公民"(first class),指的是函數(shù)與其他數(shù)據(jù)類型一樣吐葵,處于平等地位规揪,可以賦值給其他變量,也可以作為參數(shù)温峭,傳入另一個函數(shù)猛铅,或者作為別的函數(shù)的返回值。
舉例來說凤藏,下面代碼中的print變量就是一個函數(shù)奕坟,可以作為另一個函數(shù)的參數(shù)。

  var print = function(i){ console.log(i);};  
      [1,2,3].forEach(print);

2. 只用"表達式"清笨,不用"語句"
"表達式"(expression)是一個單純的運算過程月杉,總是有返回值;"語句"(statement)是執(zhí)行某種操作抠艾,沒有返回值苛萎。函數(shù)式編程要求,只使用表達式检号,不使用語句腌歉。也就是說,每一步都是單純的運算齐苛,而且都有返回值翘盖。
原因是函數(shù)式編程的開發(fā)動機,一開始就是為了處理運算(computation)凹蜂,不考慮系統(tǒng)的讀寫(I/O)馍驯。"語句"屬于對系統(tǒng)的讀寫操作,所以就被排斥在外玛痊。
當然汰瘫,實際應用中,不做I/O是不可能的擂煞。因此混弥,編程過程中,函數(shù)式編程只要求把I/O限制到最小对省,不要有不必要的讀寫行為蝗拿,保持計算過程的單純性晾捏。
3. 沒有"副作用"
所謂"副作用"(side effect),指的是函數(shù)內(nèi)部與外部互動(最典型的情況哀托,就是修改全局變量的值)粟瞬,產(chǎn)生運算以外的其他結果。
函數(shù)式編程強調(diào)沒有"副作用"萤捆,意味著函數(shù)要保持獨立裙品,所有功能就是返回一個新的值,沒有其他行為俗或,尤其是不得修改外部變量的值市怎。
4. 不修改狀態(tài)
上一點已經(jīng)提到,函數(shù)式編程只是返回新的值辛慰,不修改系統(tǒng)變量区匠。因此,不修改變量帅腌,也是它的一個重要特點驰弄。
在其他類型的語言中,變量往往用來保存"狀態(tài)"(state)速客。不修改變量戚篙,意味著狀態(tài)不能保存在變量中。函數(shù)式編程使用參數(shù)保存狀態(tài)溺职,最好的例子就是遞歸岔擂。下面的代碼是一個將字符串逆序排列的函數(shù),它演示了不同的參數(shù)如何決定了運算所處的"狀態(tài)"浪耘。

  function reverse(string) {
    if(string.length == 0) {
      return string;
    } else {
      return reverse(string.substring(1, string.length)) + string.substring(0, 1);
    }
  }

由于使用了遞歸乱灵,函數(shù)式語言的運行速度比較慢,這是它長期不能在業(yè)界推廣的主要原因七冲。
5. 引用透明
引用透明(Referential transparency)痛倚,指的是函數(shù)的運行不依賴于外部變量或"狀態(tài)",只依賴于輸入的參數(shù)澜躺,任何時候只要參數(shù)相同蝉稳,引用函數(shù)所得到的返回值總是相同的。
有了前面的第三點和第四點苗踪,這點是很顯然的颠区。其他類型的語言,函數(shù)的返回值往往與系統(tǒng)狀態(tài)有關通铲,不同的狀態(tài)之下,返回值是不一樣的器贩。這就叫"引用不透明"颅夺,很不利于觀察和理解程序的行為朋截。
三、意義
函數(shù)式編程到底有什么好處吧黄,為什么會變得越來越流行部服?
1. 代碼簡潔,開發(fā)快速
函數(shù)式編程大量使用函數(shù)拗慨,減少了代碼的重復廓八,因此程序比較短,開發(fā)速度較快赵抢。
Paul Graham在《黑客與畫家》一書中寫道:同樣功能的程序剧蹂,極端情況下,Lisp代碼的長度可能是C代碼的二十分之一烦却。
如果程序員每天所寫的代碼行數(shù)基本相同宠叼,這就意味著,"C語言需要一年時間完成開發(fā)某個功能其爵,Lisp語言只需要不到三星期冒冬。反過來說,如果某個新功能摩渺,Lisp語言完成開發(fā)需要三個月简烤,C語言需要寫五年。"當然摇幻,這樣的對比故意夸大了差異乐埠,但是"在一個高度競爭的市場中,即使開發(fā)速度只相差兩三倍囚企,也足以使得你永遠處在落后的位置丈咐。"
2. 接近自然語言,易于理解
函數(shù)式編程的自由度很高龙宏,可以寫出很接近自然語言的代碼棵逊。
前文曾經(jīng)將表達式(1 + 2) * 3 - 4,寫成函數(shù)式語言:

  subtract(multiply(add(1,2), 3), 4)

對它進行變形银酗,不難得到另一種寫法:

  add(1,2).multiply(3).subtract(4)

這基本就是自然語言的表達了辆影。再看下面的代碼,大家應該一眼就能明白它的意思吧:

  merge([1,2],[3,4]).sort().search("2")

因此黍特,函數(shù)式編程的代碼更容易理解蛙讥。
3. 更方便的代碼管理
函數(shù)式編程不依賴、也不會改變外界的狀態(tài)灭衷,只要給定輸入?yún)?shù)次慢,返回的結果必定相同。因此,每一個函數(shù)都可以被看做獨立單元迫像,很有利于進行單元測試(unit testing)和除錯(debugging)劈愚,以及模塊化組合。
4. 易于"并發(fā)編程"
函數(shù)式編程不需要考慮"死鎖"(deadlock)闻妓,因為它不修改變量菌羽,所以根本不存在"鎖"線程的問題。不必擔心一個線程的數(shù)據(jù)由缆,被另一個線程修改注祖,所以可以很放心地把工作分攤到多個線程,部署"并發(fā)編程"(concurrency)均唉。
請看下面的代碼:

  var s1 = Op1();
  var s2 = Op2();
  var s3 = concat(s1, s2);

由于s1和s2互不干擾是晨,不會修改變量,誰先執(zhí)行是無所謂的浸卦,所以可以放心地增加線程署鸡,把它們分配在兩個線程上完成。其他類型的語言就做不到這一點限嫌,因為s1可能會修改系統(tǒng)狀態(tài)靴庆,而s2可能會用到這些狀態(tài),所以必須保證s2在s1之后運行怒医,自然也就不能部署到其他線程上了炉抒。
多核CPU是將來的潮流,所以函數(shù)式編程的這個特性非常重要稚叹。
5. 代碼的熱升級
函數(shù)式編程沒有副作用焰薄,只要保證接口不變,內(nèi)部實現(xiàn)是外部無關的扒袖。所以塞茅,可以在運行狀態(tài)下直接升級代碼,不需要重啟季率,也不需要停機野瘦。Erlang語言早就證明了這一點,它是瑞典愛立信公司為了管理電話系統(tǒng)而開發(fā)的飒泻,電話系統(tǒng)的升級當然是不能停機的鞭光。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市泞遗,隨后出現(xiàn)的幾起案子惰许,更是在濱河造成了極大的恐慌,老刑警劉巖史辙,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汹买,死亡現(xiàn)場離奇詭異佩伤,居然都是意外死亡,警方通過查閱死者的電腦和手機卦睹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門畦戒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來方库,“玉大人结序,你說我怎么就攤上這事∽萘剩” “怎么了徐鹤?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長邀层。 經(jīng)常有香客問我返敬,道長,這世上最難降的妖魔是什么寥院? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任劲赠,我火速辦了婚禮,結果婚禮上秸谢,老公的妹妹穿的比我還像新娘凛澎。我一直安慰自己,他們只是感情好估蹄,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布塑煎。 她就那樣靜靜地躺著,像睡著了一般臭蚁。 火紅的嫁衣襯著肌膚如雪最铁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天垮兑,我揣著相機與錄音冷尉,去河邊找鬼。 笑死系枪,一個胖子當著我的面吹牛雀哨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嗤无,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼震束,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了当犯?” 一聲冷哼從身側響起垢村,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嚎卫,沒想到半個月后嘉栓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宏榕,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年侵佃,在試婚紗的時候發(fā)現(xiàn)自己被綠了麻昼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡馋辈,死狀恐怖抚芦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情迈螟,我是刑警寧澤叉抡,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站答毫,受9級特大地震影響褥民,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洗搂,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一消返、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耘拇,春花似錦撵颊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挣棕,卻和暖如春译隘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洛心。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工固耘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人词身。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓厅目,卻偏偏與公主長得像,于是被迫代替她去往敵國和親法严。 傳聞我的和親對象是個殘疾皇子损敷,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容