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