物件-函數語言程式設計簡史

edithfang發表於2014-06-26

前言:
從前,有一種程式語言叫Scala。
人們研究這種語言,發現這是一種“給人印象深刻”的語言,但是由於這種語言的功能特徵不斷的急速進化,導致除了一些自己研究的專案外,沒有其他人再使用這種語言開發了。
這種語言看起來很美,但沒有人願意冒險把自己的職業生涯依賴於這種語言上,這個語言太年輕了,誰能保證它不會夭折?
之後,發生了一些事情; Scala 長大了。 Twitter 宣佈他們用Scala語言替換了以前一些用Ruby開發的後端程式,而SAP也在使用這種語言,還有EDF等。 這訊息迅速傳播開來,有許多新的程式開發者慕名而來,他們也都感覺到這是一種“令人印象深刻”的語言,同時,早期的這個語言的信徒也開始發現此語言已經鳳凰涅磐,讓他們眼睛一亮。
他們現在看到的這種語言已經是一個成熟的、急不可待的等人們使用它去大展巨集圖的語言了。 隨著2.8版本的釋出,Scala 終於從少年進入了青年,可以當之無愧的接受“令人印象深刻”的讚譽了。

程式設計就是人生

程式語言在進化,在繁衍,產生不同的種族。 非常類似於生命在早期地球的上的構成,程式語言最初是誕生於由CPU指令和數學概念混成的沸騰的高湯裡。 跟生命的發展不同的是,它們不需要泥土。
但是它們也經歷著殘酷血腥的優勝劣汰、物競天擇過程,當然,你可以把它們之間的戰爭想像成關於Tab鍵和Space鍵,關於括弧在程式中的地位問題上的戰爭 。
人們就像一個優秀的飼養員只喜歡挑選一個純種血統的良馬一樣選擇自己喜愛的程式語言。 就像生物學上,人工育種必然存在不足,近親連續不斷地繁殖、以此來儲存某一血統的令人滿意的特性的做法必然潛藏基因缺陷的危險。
幸運的事,凡事都有兩面, 好的飼養人和園丁會選擇利用 混血雜交優勢.
父母的結合產生的後代匯聚了其父母雙方各自不同的特徵,所以後代比前代更強大,同時父母各自的弱點也會被後代查明從而摒棄。 同樣的思想也被應用到了程式語言的世界裡[2],各種物件導向和麵向函式風格的概念相互融合給予了程式設計師們前所未有的能力和表達方式。
Scala程式語言就是這樣的語言中的一員。

[1]To the best of my knowledge, although there was a bit of dust.
[2]Memes can also evolve…

言歸正傳!

我估計閱讀我這篇文章的大部分是Java程式設計師,所以在我詳細的解釋函式和物件如何互動之前我打算先介紹一些關於針對函式程式設計的概念。
其實在網上已經有了很多完全超出我的寫作水平的好教材,所以我願意儘量簡單的介紹一下。

什麼是函式?

數學裡,函式就是接受一個值(輸入值)而後使用它產生另外一個值(輸出)的運算。 在很長的時間裡這個定義幾乎適用有所有任何的情況,即使是現在,數學家們也只是在擴充這個定義裡的“值”的概念: 複雜數值,矩陣,向量,座標(對稱座標和笛卡爾座標),四元數。.. 很多東西都可以被當作“值”,只要你用正確的方式去看待它。
這種情況持續了很久,之後程式設計師出現了,之後計算機被髮明瞭。
一旦人們對計算機技術的重要性達成共識,並且使計算機技術逐步完善起來,程式設計師就開始用一種新的思想考慮他們了,比如:看著這計算機列印輸出的長河般一排排的三個字母組成的彙編程式碼,你不頭痛也不行。
如果他們能把那些序列碼按相同的功能分成一組一組,給它們起個名稱,那麼他們將會有一種簡潔的方式去重複利用這些程式碼,那麼以前花大量時間拼寫這些程式碼的時間節省下來,終於有了去酒館的時間。 因為很多的程式設計師也都是數學家,因為很多他們的程式都是用來解決數學問題的,這就決定了函式的概念非常簡潔的迎合了這種給程式設計單元打包處理的行為,從此第二代程式語言誕生了。

[3]Yes, really.  Ada Lovelace was doing her thing before Babbage assembled so much as a single gear.

完美中的不足…

這種革新,完美中有些不足。 針對函式,人們發現一個問題,就是經常需要它們一次處理多個輸入值,或,更令人沮喪的,多個輸出值。 幸運的是一些數學家解決了如果讓函式處理多個輸入值的問題,這種思想很早就被人採納了,人們按照這種思路想出來如何去返回多個輸出值(通常是把輸入值給抹去,替換我想要輸出的值)。 但是其他的一些數學家(例如Haskell)並不喜歡多個輸入值的方式,他產生了一個新的觀點,用高階函式替代多個輸入值,函式可以返回其它函式,或可以用函式體當作函式引數,但這種做法很難實現,所以程式設計師起初都沒在意這種觀點。
函式程式設計還有一個問題,就是它有副作用。 一個函式使用一個相同輸入值(例如讀一個檔案)卻可以每次都做出不同的事情,或者它可以去做一些不專一的事情(例如處理返回一個值外還會向控制檯列印一行字)。 更糟糕的是,它會把自己的輸入值在使用之後改變其值! 對於那些想利用這些副作用的人來說,這是再好不過了,可是對於另外的一些數學家就不一樣了,他們不喜歡喝啤酒,可是還必須要把啤酒杯拿在手上。
所以程式裡的函式跟數學裡的函式是不同的。 人們給出了一個新的定義(不是很精確的):一個程式,或者一批指令,具有一個名稱,可以選擇性的擁有一個或多個輸入值和輸出值,甚至同時具備多個輸入值和多個輸出值,同時還能做點額外的事情。

A reprive ahead of its time

自然,很多數學家並不高興函式被定義成這樣,於是一個新的語言品種被創造了出來,用來彌補其先天的不足,再一次的將它用一個穩固的理論架構確定下來。
函式體成為第一類實體,而非以前的僅是一批程式碼的別名。 這樣Haskell的高階函式的概念就可以應用於設計開發軟體了。 程式語言的進化發展中人們越來越多的鼓勵使用常量值,這樣函式就不能把輸入值給能髒了。 人們實現了區域性套用(Currying),開始使用陣列結構,這樣函式終於又回到了只能接受一個輸入值和一個輸出值的紳士面貌。 一些有趣的方法被人們採用來限制那些討厭的”副作用“:如果這些副作用不能完全避免的話,那就把它們規整起來專門找個地方放置它們。 這樣的語系被人們稱作為“函式式”程式語言,因為它們把函式的概念迴歸到了其數學上的根源。 這個語系裡的語言包括有Lisp, Scheme, Caml, Erlang, F#, Clojure等。
作為工程學上一個優秀的典範,函式式語言具有設計優良,易理解,高效,結構穩定等優點。 與此同時,如同其他Good Ideas?經常遇到的情況一樣,很長的一段時間裡它們被主流團體所遺忘。 程式設計師們都很清楚為什麼人們喜歡把函式放在首要位置; 人們需要把系統按單元功能劃分,相互不依賴,可以在不同的地方重複使用它們。 這些願望就像癢癢需要撓的感覺折磨著人們,於是物件導向的思維從此誕生了並崛起了。
目前,函數語言程式設計只是被人們當成一種業餘愛好,也被人們用在相關的演講和論文裡去靈巧的闡述一些新事物。 人們通常認為函式式語言會比命令式語言執行的慢,但這種結論也許只有上帝知道,因為從來沒有人用自己的方式證明過。 人們還認為,儘管函式式語言看起來非常簡潔,適合小的程式和做演示用,但它們不太適合大規模的程式,像那些成百上千行的程式,如果用函式式語言來開發,幾乎是不可維護的。

[4]Often using monads
[5]Except, maybe, for (all(the(parenthesis())))
[6]at least, those who wanted to be pragmatic so they could get more beer time
[7]But that’s a different story…


重生

實際上,函式式語言並不只是一種玩物。 跟隨著時代革新的大潮,它在地下醞釀了這麼多年,終於等到了這個世界可是接受它的這一天。 主流程式設計師們越來越多的認識到,函式式語言是如此的容易使用,而這一點在其它(物件導向)程式碼是難以達到的。 就比如這個簡單的問題“處理這個字串佇列,將它們全部轉化成大寫後返回”,用Java編寫卻有可能出錯。 因為偶爾人們會忽略掉這個佇列裡的第一個字串,因為他們從1開始計數,而不是0。有時候人們會發現這個佇列裡的字串不是按他們要求的部分轉化成大寫,而是全部大寫了,還有些時候程式會報出空指標異常。
逐漸的,人們開始討論起closures和continuations,為的是讓他們的程式更加的強壯和可維護。 當時這些東西並不是物件們所能具有的,於是加強型for迴圈被髮明瞭,還有匿名類,visitor模式,command 模式。 當然這些沒有一個能按照程式設計師們想象的那樣的完美,但這些東西還是有用的,讓很多有問題的地方變得可維護了(即使這樣需要編排一些醜陋的模板式的程式碼)。 時機已經到了人們改變思維方式的時候了,函式式語言已經迫不及待的看到自己的巨集大入場了。

[8]Though not the null pointer exceptions

讓人嫉妒的特性

通過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 新增)

[9]Except for the scala library import
[10]No more of those endless get/set methods to litter your code
[11]Actually it is, but you have to do tricky stuff with reflection.[12]These methods don’t use the same cumbersome naming convention as Java’s bean accessors, although generation of such accessors can be explicitly requested by using the @BeanProperty annotation.


所有的這些都意味著什麼?

函數語言程式設計已經證實了它的實力,快速增長的開發者人數是最好的證明。
Scala向大家演示瞭如何在不犧牲物件導向思維模式下接受函式式設計模式的概念。 它同時也向大家顯示瞭如果將這兩種風格的語言如何融合到一起變成一個強壯豐滿的新語言,不帶任何的形式的勉強。
一旦你瞭解了基本語法並對閉包、第一類屬性、高階函式、traits,、immutable refs等概念有了認識,它的各種特點的相互結合會向你展示它更深層次的潛質。 語言生命裡的一些設計思想的選擇和確定最終導致了一個增效作用;
我們認定這種新一代的物件-函式式的設計正是使Scala今天如此成功的關鍵。
With future articles, I hope to further explain this exciting language by covering some of the ideas that are helping to spread its growing popularity.

[13]I’m proud to rescue this word from its mindless enslavement by corporate jargon. Terminology is important to us developers, we should fight for our political prisoners!
About the Blogger

Kevin Wright has finally settled back in London to work on market analysis for the telecoms industry after having worked his way around Europe in manufacturing, finance and even online gaming. He’s a self-appointed Scala Evangelist and an active participant in every forum he can find, where he’s currently trying to build interest in the London Scala Users’ Group.

本文轉載自:外刊IT評論
相關閱讀
評論(0)

相關文章