對象函數(shù)式編程 Scala簡史
從前,有一種編程語言叫Scala,人們研究這種語言,發(fā)現(xiàn)這是一種給人印象深刻的語言,有人說與Scala接觸的最大感受就是Java如影相隨。這種語言看起來很美,但沒有人愿意冒險把自己的職業(yè)生涯依賴于這種語言上,這個語言太年輕了,誰能保證它不會夭折?
之后,發(fā)生了一些事情; Scala 長大了。 Twitter 宣布他們用Scala語言替換了以前一些用Ruby開發(fā)的后端程序,而SAP也在使用這種語言,還有EDF等。 這消息迅速傳播開來,有許多新的程序開發(fā)者慕名而來,他們也都感覺到這是一種令人印象深刻的語言,同時,早期的這個語言的信徒也開始發(fā)現(xiàn)此語言已經(jīng)鳳凰涅磐,讓他們眼睛一亮。
他們現(xiàn)在看到的這種語言已經(jīng)是一個成熟的、急不可待的等人們使用它去大展宏圖的語言了。 隨著2.8版本的發(fā)布,Scala 終于從少年進(jìn)入了青年,可以當(dāng)之無愧的接受令人印象深刻的贊譽(yù)了。
編程就是人生
程序語言在進(jìn)化、在繁衍,產(chǎn)生不同的種族。非常類似于生命在早期地球的上的構(gòu)成,編程語言最初是誕生于由CPU指令和數(shù)學(xué)概念混成的沸騰的高湯里。 跟生命的發(fā)展不同的是,它們不需要泥土,但是它們也經(jīng)歷著殘酷血腥的優(yōu)勝劣汰、物競天擇過程,當(dāng)然,你可以把它們之間的戰(zhàn)爭想像成關(guān)于Tab鍵和Space鍵,關(guān)于括弧在程序中的地位問題上的戰(zhàn)爭 。
人們就像一個優(yōu)秀的飼養(yǎng)員只喜歡挑選一個純種血統(tǒng)的良馬一樣選擇自己喜愛的編程語言。 就像生物學(xué)上,人工育種必然存在不足,近親連續(xù)不斷地繁殖、以此來保存某一血統(tǒng)的令人滿意的特性的做法必然潛藏基因缺陷的危險。
父母的結(jié)合產(chǎn)生的后代匯聚了其父母雙方各自不同的特征,所以后代比前代更強(qiáng)大,同時父母各自的弱點也會被后代查明從而摒棄。 同樣的思想也被應(yīng)用到了編程語言的世界里,各種面向?qū)ο蠛兔嫦蚝瘮?shù)風(fēng)格的概念相互融合給予了程序員們前所未有的能力和表達(dá)方式,Scala編程語言就是這樣的語言中的一員。
我估計閱讀我這篇文章的大部分是Java程序員,所以在我詳細(xì)的解釋函數(shù)和對象如何交互之前我打算先介紹一些關(guān)于針對函數(shù)編程的概念。其實在網(wǎng)上已經(jīng)有了很多完全超出我的寫作水平的好教材,所以我愿意盡量簡單的介紹一下。
什么是函數(shù)?
數(shù)學(xué)里,函數(shù)就是接受一個值(輸入值)而后使用它產(chǎn)生另外一個值(輸出)的運(yùn)算。 在很長的時間里這個定義幾乎適用有所有任何的情況,即使是現(xiàn)在,數(shù)學(xué)家們也只是在擴(kuò)充這個定義里的值的概念:復(fù)雜數(shù)值,矩陣,向量,坐標(biāo)(對稱坐標(biāo)和笛卡爾坐標(biāo)),四元數(shù)。.. 很多東西都可以被當(dāng)作值,只要你用正確的方式去看待它。
這種情況持續(xù)了很久,之后程序員出現(xiàn)了,之后計算機(jī)被發(fā)明了。
一旦人們對計算機(jī)技術(shù)的重要性達(dá)成共識,并且使計算機(jī)技術(shù)逐步完善起來,程序員就開始用一種新的思想考慮他們了,比如:看著這計算機(jī)打印輸出的長河般一排排的三個字母組成的匯編程序碼,你不頭痛也不行。
如果他們能把那些序列碼按相同的功能分成一組一組,給它們起個名稱,那么他們將會有一種簡潔的方式去重復(fù)利用這些代碼,那么以前花大量時間拼寫這些代碼的時間節(jié)省下來,終于有了去酒館的時間。 因為很多的程序員也都是數(shù)學(xué)家,因為很多他們的程序都是用來解決數(shù)學(xué)問題的,這就決定了函數(shù)的概念非常簡潔的迎合了這種給編程單元打包處理的行為,從此第二代編程語言誕生了。
完美中的不足
這種革新,完美中有些不足。 針對函數(shù),人們發(fā)現(xiàn)一個問題,就是經(jīng)常需要它們一次處理多個輸入值,或,更令人沮喪的,多個輸出值。 幸運(yùn)的是一些數(shù)學(xué)家解決了如果讓函數(shù)處理多個輸入值的問題,這種思想很早就被人采納了,人們按照這種思路想出來如何去返回多個輸出值(通常是把輸入值給抹去,替換我想要輸出的值)。 但是其他的一些數(shù)學(xué)家(例如Haskell)并不喜歡多個輸入值的方式,他產(chǎn)生了一個新的觀點,用高階函數(shù)替代多個輸入值,函數(shù)可以返回其它函數(shù),或可以用函數(shù)體當(dāng)作函數(shù)參數(shù),但這種做法很難實現(xiàn),所以程序員起初都沒在意這種觀點。
函數(shù)編程還有一個問題,就是它有副作用。 一個函數(shù)使用一個相同輸入值(例如讀一個文件)卻可以每次都做出不同的事情,或者它可以去做一些不專一的事情(例如處理返回一個值外還會向控制臺打印一行字)。 更糟糕的是,它會把自己的輸入值在使用之后改變其值! 對于那些想利用這些副作用的人來說,這是再好不過了,可是對于另外的一些數(shù)學(xué)家就不一樣了,他們不喜歡喝啤酒,可是還必須要把啤酒杯拿在手上。
所以程序里的函數(shù)跟數(shù)學(xué)里的函數(shù)是不同的。 人們給出了一個新的定義(不是很精確的):一個程序,或者一批指令,具有一個名稱,可以選擇性的擁有一個或多個輸入值和輸出值,甚至同時具備多個輸入值和多個輸出值,同時還能做點額外的事情。 #p#
A reprive ahead of its time
自然,很多數(shù)學(xué)家并不高興函數(shù)被定義成這樣,于是一個新的語言品種被創(chuàng)造了出來,用來彌補(bǔ)其先天的不足,再一次的將它用一個穩(wěn)固的理論架構(gòu)確定下來。函數(shù)體成為第一類實體,而非以前的僅是一批代碼的別名。 這樣Haskell的高階函數(shù)的概念就可以應(yīng)用于設(shè)計開發(fā)軟件了。 編程語言的進(jìn)化發(fā)展中人們越來越多的鼓勵使用常量值,這樣函數(shù)就不能把輸入值給能臟了。 人們實現(xiàn)了局部套用(Currying),開始使用數(shù)組結(jié)構(gòu),這樣函數(shù)終于又回到了只能接受一個輸入值和一個輸出值的紳士面貌。 一些有趣的方法被人們采用來限制那些討厭的副作用:如果這些副作用不能完全避免的話,那就把它們規(guī)整起來專門找個地方放置它們。 這樣的語系被人們稱作為函數(shù)式編程語言,因為它們把函數(shù)的概念回歸到了其數(shù)學(xué)上的根源。 這個語系里的語言包括有Lisp, Scheme, Caml, Erlang, F#, Clojure等。
作為工程學(xué)上一個優(yōu)秀的典范,函數(shù)式語言具有設(shè)計優(yōu)良,易理解,高效,結(jié)構(gòu)穩(wěn)定等優(yōu)點。 與此同時,如同其他Good Ideas?經(jīng)常遇到的情況一樣,很長的一段時間里它們被主流團(tuán)體所遺忘。 程序員們都很清楚為什么人們喜歡把函數(shù)放在首要位置; 人們需要把系統(tǒng)按單元功能劃分,相互不依賴,可以在不同的地方重復(fù)使用它們。 這些愿望就像癢癢需要撓的感覺折磨著人們,于是面向?qū)ο蟮乃季S從此誕生了并崛起了。
目前,函數(shù)式編程只是被人們當(dāng)成一種業(yè)余愛好,也被人們用在相關(guān)的演講和論文里去靈巧的闡述一些新事物。 人們通常認(rèn)為函數(shù)式語言會比命令式語言運(yùn)行的慢,但這種結(jié)論也許只有上帝知道,因為從來沒有人用自己的方式證明過。 人們還認(rèn)為,盡管函數(shù)式語言看起來非常簡潔,適合小的程序和做演示用,但它們不太適合大規(guī)模的程序,像那些成百上千行的程序,如果用函數(shù)式語言來開發(fā),幾乎是不可維護(hù)的。
重生
實際上,函數(shù)式語言并不只是一種玩物。 跟隨著時代革新的大潮,它在地下醞釀了這么多年,終于等到了這個世界可是接受它的這一天。 主流程序員們越來越多的認(rèn)識到,函數(shù)式語言是如此的容易使用,而這一點在其它(面向?qū)ο螅┐a是難以達(dá)到的。 就比如這個簡單的問題處理這個字符串隊列,將它們?nèi)哭D(zhuǎn)化成大寫后返回,用Java編寫卻有可能出錯。 因為偶爾人們會忽略掉這個隊列里的第一個字串,因為他們從1開始計數(shù),而不是0。有時候人們會發(fā)現(xiàn)這個隊列里的字串不是按他們要求的部分轉(zhuǎn)化成大寫,而是全部大寫了,還有些時候程序會報出空指針異常。
逐漸的,人們開始討論起closures和continuations,為的是讓他們的程序更加的強(qiáng)壯和可維護(hù)。 當(dāng)時這些東西并不是對象們所能具有的,于是加強(qiáng)型for循環(huán)被發(fā)明了,還有匿名類,visitor模式,command 模式。 當(dāng)然這些沒有一個能按照程序員們想象的那樣的完美,但這些東西還是有用的,讓很多有問題的地方變得可維護(hù)了(即使這樣需要編排一些丑陋的模板式的代碼)。 時機(jī)已經(jīng)到了人們改變思維方式的時候了,函數(shù)式語言已經(jīng)迫不及待的看到自己的宏大入場了。
讓人嫉妒的特性
通過Erlang語言,愛立信演示了函數(shù)式語言如何能應(yīng)用于大規(guī)模系統(tǒng)的。 而其開發(fā)效率高,可維護(hù)性,可測試性都很好,特別是不易犯錯。 這才是真正的函數(shù)式語言的面貌,感覺比面向?qū)ο笳Z言要成功的多。 愛立信的程序員們前所未有的有了充分的喝啤酒的空閑時間了。 生活變得輕松起來!
而在另外的陣營里的程序員看待函數(shù)式語言有點想法,也有的嫉妒。 Java變得如此臃腫,而且,每一個新出現(xiàn)的特征都看起來是圍繞著它的模板代碼風(fēng)格創(chuàng)造出來的。 即使是很小的程序,現(xiàn)在也要使用annotations,模板參數(shù),和duplicate type declarations,大程序問題就更大了。 不幸中的不幸,關(guān)于如何往Java里添加closures(閉包)功能的討論并不像早期預(yù)期的那樣順利,還有,Java bean里的數(shù)不完的get/set方法實在是不能在忍受了。
有些事情必須要變了。
除了這些,Java還有一大堆的問題。 The Virtual Machine(虛擬機(jī))是一個非常成熟的工具,經(jīng)過了很好的優(yōu)化,市場上隨處可見,從洗衣機(jī),移動電話,到數(shù)不清的web服務(wù)器和桌面電腦里都有它的身影。 Java系統(tǒng)在開源庫和框架方面已經(jīng)發(fā)展的令人瞠目結(jié)舌繁華,在一些付費(fèi)系統(tǒng)里也火的不得了。 靠著Java這棵大樹,市面上已經(jīng)到處都是由各種企業(yè)投資推動的數(shù)不清的團(tuán)隊開發(fā)工作創(chuàng)造出的成功和成熟的java項目,如果因為一些小的語言特征而放棄Java這一切基本是不可能的。
我們一起做蛋糕 也一起吃
我們所有做的事既要繼承Java所有目前的優(yōu)質(zhì)資產(chǎn),同時也要使用函數(shù)式語言重新描繪新的編程語言版圖,Scala正好迎合了這種需要,盡管它有很多的競爭對手。 Pizza語言第一個出現(xiàn)的,但它跟今天的Scala比較起來更Scala當(dāng)初的形式。
我們所知的能在JVM上跑的語言大概有JavaFX, JRuby, Jython, Groovy 等。 大部分都有closures 和其他的一些函數(shù)式語言具有的特征,但在Java王國里,這些新生事物并不是那么的血統(tǒng)純正,它們的特征更像是外來移民,護(hù)照很新亮,但有異域口音。
動態(tài)語言的流行是無濟(jì)于事的; 類型可以通過各種方法隱藏起來,讓人感到它的不存在,但是這樣很難編譯出原生的Java代碼了。 這是個很大的問題,特別是你寫出的對象需要拿到第三方類庫里去處理時。 有時候各種語言之間很難交互,通常需要一個解釋器,就像JSR233 Scripting API 或 the Bean Scripting Framework 那樣。Scala卻有與生俱來的優(yōu)勢,它和Java的結(jié)合是如此的緊密,它能像自己本身的類型那樣處理Java類型,它并不像一個外來移民,而是一個僑胞,而且是有護(hù)照的公民。
你從外面看,Java和Scala編譯出來的代碼是一模一樣的,沒有區(qū)別,這有點讓人難以置信,但可以明確的告訴大家,Scala最初就是這樣設(shè)計出來的。 當(dāng)你把Scala當(dāng)作一種函數(shù)式語言時,你會更驚奇的發(fā)現(xiàn),它把面向?qū)ο蠛秃瘮?shù)式的兩種風(fēng)格以其優(yōu)雅的方式完全融合統(tǒng)一起來。
正因為它和Java是如此緊密的聯(lián)系,你可以把Scala當(dāng)作Java臨時的替代品,它絕對不會強(qiáng)制你用任何的函數(shù)式風(fēng)格的代碼書寫。 它的類型引用,簡潔的屬性存取,以及帶有成員變量參數(shù)的構(gòu)造函數(shù),你幾乎可以把它當(dāng)作一種風(fēng)格簡潔的Java。 除了上述的優(yōu)點外,我們可以稱贊Scala為某些方便比Java面向?qū)ο蟾晒Φ恼Z言:
一切皆為對象,包括數(shù)值和函數(shù)
在Java中,方法不是對象,更別提基本數(shù)據(jù)類型了。 2.toString在Scala里是一個合法的語句。它拋棄了靜態(tài)類成員,Java的這個問題可以追溯到它所效仿的C++上,是個歷史錯誤。 C++本身就是個混合型的語言,它的設(shè)計目標(biāo)就是要兼容過程式的C語言,同時也要支持對象結(jié)構(gòu)。 靜態(tài)成員不是完全的可面向?qū)ο?,因為他們不能實現(xiàn)接口,以及向普通成員那樣的多形性和覆蓋、過載。 當(dāng)你把一個對象當(dāng)作參數(shù)傳入一個函數(shù)時,靜態(tài)成員是不可用的。
相反,Scala提供了singleton objects, 這樣這種問題就不存在了。 Scala里新的companion概念可以讓你使用singleton去訪問具有相同類名的實例上的一個有約束限定的成員,這樣你就可以把靜態(tài)成員的權(quán)限復(fù)制出來。
類上所有的屬性都實現(xiàn)了behind-the-scenes,就像是個隱藏域,而且有針對它的一對Get和Set隱藏方法。 那些任何人都可以直接修改的內(nèi)部屬性將不再被允許公共訪問,在將來,虛擬類的概念將會在Scala里出現(xiàn),那樣后Scala對對象的支持將會有更驚人的表現(xiàn)。函數(shù)式編程對下面的特征進(jìn)行了支持:
◆對遞歸函數(shù)的尾調(diào)用(tail-call)優(yōu)化
◆模式匹配
◆第一類函數(shù)和高級函數(shù)
◆局部函數(shù)(可以接受任何輸入值)
◆局部套用(Currying)和函數(shù)局部應(yīng)用
◆閉包
◆簡潔的聲明常量值的語法定義,很好的支持常量集合的類庫
◆continuations (scala 2.8 新增)
所有的這些都意味著什么?
函數(shù)式編程已經(jīng)證實了它的實力,快速增長的開發(fā)者人數(shù)是最好的證明,Scala向大家演示了如何在不犧牲面向?qū)ο笏季S模式下接受函數(shù)式設(shè)計模式的概念。 它同時也向大家顯示了如果將這兩種風(fēng)格的語言如何融合到一起變成一個強(qiáng)壯豐滿的新語言,不帶任何的形式的勉強(qiáng)。
一旦你了解了基本語法并對閉包、第一類屬性、高階函數(shù)、traits,、immutable refs等概念有了認(rèn)識,它的各種特點的相互結(jié)合會向你展示它更深層次的潛質(zhì)。 語言生命里的一些設(shè)計思想的選擇和確定最終導(dǎo)致了一個增效作用,我們認(rèn)定這種新一代的對象-函數(shù)式的設(shè)計正是使Scala今天如此成功的關(guān)鍵。
【編輯推薦】