天天看點

《冒号課堂》連載之十二——超級範式

冒号課堂》連載之十二——超級範式:提升語言的級别
《冒号課堂》連載之十二——超級範式

3.2  超級範式——提升語言的級别

智能繁衍:機器人生産機器人。

——題記

關鍵詞:程式設計範式;模闆元程式設計;元程式設計;語言導向式程式設計;産生式程式設計

摘  要:元程式設計簡談

預覽

元程式設計作為超級範式的一個展現是,它能提升語言的級别。

如果說OOP的關鍵在于構造對象的概念,那麼LOP的關鍵在于構造語言的文法。

離開IDE就無法編寫、編譯或調試的程式員,如同卸盔下馬後便失去戰鬥力的武士,是殘缺和孱弱的。

既然有重複的代碼,不能從文法上提煉,不妨退一步從文字上提煉。

元程式将程式作為資料來對待,能自我發現、自我賦權和自我更新,有着其他程式所不具備的自覺性、自适應性和智能性,可以說是一種最進階的程式。

提問

什麼是元程式設計?它與通常的程式設計有何不同?

元程式設計有何用處?它有哪些應用?

相比自編的元程式,用IDE自動生成的代碼有什麼缺陷?

語言導向式程式設計有何優點?它與元程式設計有何關系?

元程式設計與産生式程式設計有何異同?

為什麼說元程式是一種最進階的程式?

講解

問号忽然想起一事,問道:“有一本名為《C++模版元程式設計》的書,既然提到了模闆,想來也屬于泛型程式設計吧?”

冒号答道:“模闆元程式設計即Template Metaprogramming,與泛型程式設計密切相關但自成一派,隸屬于另一種程式設計範式——元程式設計(Metaprogramming),簡稱MP。此處的字首‘meta-’常譯作‘元’,其實就是‘超級’、‘行而上’的意思。比如,中繼資料(Metadata)是關于資料的資料,元對象(Metaobject)是關于對象的對象,依此類推,元程式設計自然是關于程式的程式,或者說是編寫、操縱程式的程式。”

歎号皺着眉:“聽起來有點繞。”

冒号投影出如下代碼——

C++(元程式設計):

template <int N>

struct factorial

{

     enum { value = N * factorial<N - 1>::value };

};

template <>              // 特化(specialization)

struct factorial<0>    // 遞歸中止

{

     enum { value = 1 };

};

void main()

{

    // 以下等價于 cout << 120 << endl;

    cout << factorial<5>::value << endl;

}

“以上用模闆元程式設計實作了階乘運算。”冒号講解道,“與前面3種核心範式的階乘實作有着根本的不同:這裡階乘的值是在編譯時而非運作時計算出來的。換句話說,這段代碼以模闆形式通過編譯器生成了新的代碼,并在編譯期間獲得執行。”

歎号大惑不解:“這又說明什麼呢?”

冒号并不直接回答:“假設你需要批量處理使用者文檔,其格式結構預先給定,但既不像CSV(逗号分隔)那麼簡單,也不像XML那麼标準,并且使用者随時可能改變格式标準,請問如何設計這段程式?”

歎号略一思索,便回答:“3大子產品:閱讀器讀出輸入文檔,解析器按照格式标準去解析,處理器對解析結果進行處理。”

“顯然關鍵在解析器,如果從頭做起,那麼問題至少有4個。”冒号扳着指頭數,“第一,費時寫解析器代碼;第二,費時調試解析器代碼;第三,如果使用者更改格式标準,你得重複做上兩件事;第四,如果這段程式是大型程式的一部分,任何改動都可能意味着軟體的重新編譯、連接配接、測試、打包、部署,等等。如果因為你的緣故公司不得不頻頻釋出更新檔包的話,你的飯碗恐怕是朝不保夕了。”

還是句号機靈:“既然談到了元程式設計,一定是利用元程式設計,根據不同的格式标準自動生成相應的解析器代碼。不過——此法雖一勞永逸,但難度似乎不小啊。”

“思路對頭!”冒号贊許道,“大家聽說過Lex和Yacc嗎?它們能根據格式标準生成相應的解析器代碼。更妙的是,格式标準不限于靜态資料,甚至可以含有動态指令!這意味着使用者不僅能定義業務資料格式,還能定義業務流程。”

“這敢情好!”歎号興奮地說。

“如果知道Lex和Yacc本來就是編寫編譯器和解釋器的工具,你就不會驚訝于它們的強大了。順帶說一句,編譯器本身就是元程式設計的典型範例——把進階語言轉化為彙編語言或機器語言的程式,不就是能寫程式的程式嗎?”冒号引申開來,“更進一步地,我們可以定義自己的領域特定語言DSL,更加靈活友善地處理客戶邏輯。”

逗号有點糊塗了:“領域特定語言?就是前兩堂課提到的非通用程式設計語言吧?怎麼和元程式設計也扯上關系了?”

“不是扯上關系,而是它們之間本來就有着千絲萬縷的聯系。”冒号糾正着,“相比第3代的通用程式設計語言,領域特定語言由于其在應用範圍上和文法上的限制而顯得簡單、針對性強,有時被成為‘小語言’(little language),也是一種特進階語言(very high-level programming language,簡稱VHLL),屬于第4代程式設計語言。”

冒号說到此處,逗号猛地一拍腦門:“哦,我明白了。第4代語言最終須要編譯為機器語言,而編譯器就是元程式設計的應用。”

“你隻說對了一半。”冒号不疾不緩地說,“DSL一般不會一步到位地編譯為第1代的機器語言或第2代的彙編語言,而是通過現成的編譯器生成器(compiler-compiler或compiler generator)首先轉化為第3代的進階語言。這樣不僅大大降低了難度,也友善了程式的調試。剛才提到的Yacc(Yet Another Compiler Compiler)便是這樣的工具,能為解析器(parser)産生C程式,多用于Unix下的程式設計。更現代的工具如ANTLR (ANother Tool for Language Recognition),能生成C、C++、Java、C#、Python等多種語言的源程式。”

引号立刻聯想到:“我記得架構Hiberate的必備庫中就含有antlr.jar檔案,與這個ANTLR有關嗎?”

“正是!”冒号很滿意學員完美的配合,“Hiberate中的HQL(Hibernate Query Language)是典型的DSL,須要通過ANTLR來解析。你們可以驗證一下,在Hibernate的API中有org.hibernate.hql.antlr的package,但在其釋出的源代碼中相應的目錄下卻看不到一個Java源檔案。卻是為何?蓋是以package中所有的源代碼都是在ant build中自動生成的,這些非人工編輯的檔案是不會放在版本控制中的。”

衆人茅塞頓開。

句号想通了一個邏輯:“元程式設計作為超級範式的一個展現是,它能提升語言的級别。比如,有了編譯器的存在,彙編語言更新為第3代進階語言;同樣借助Yacc、ANTLR之類的元程式設計工具,第3代語言可以更新為第4代的DSL語言。”

冒号并未就此止步:“将這一模式發揮到極緻,便是更加激進的語言導向式程式設計[1](Language-Oriented Programming,簡稱LOP)。這種程式設計範式的思路是:在建立一套DSL體系之後,直接用它們來編寫軟體,盡量不用通用語言。”

歎号莫明其妙:“想法近乎瘋狂啊!放着好端端的通用語言不用,先造一套專用語言,這麼做劃算嗎?”

“如果一個大型系統涉及的領域十分專業,包含的業務邏輯十分複雜,為其定制DSL或許會磨刀不誤砍柴工。我們通過圖3-1和圖3-2比較一下這種範式與主流程式設計範式的不同之處。”冒号映出新的投影——

《冒号課堂》連載之十二——超級範式

圖3-2  專用語言程式設計

“由于DSL比通用語言更簡單、更抽象、更專業、更接近自然語言和聲明式語言,開發效率顯著提高,是以圖中手工部分的時間相應減少。此外尤為關鍵的是,這種方式填補了專業程式員與業務分析員之間的鴻溝。要求一個非專業程式設計的業務分析員用DSL來開發固是勉為其難,但要做到讀懂代碼并審查其中的業務邏輯則已非難事。”冒号細解個中要點,“如果說OOP的關鍵在于構造對象的概念,那麼LOP的關鍵在于構造語言的文法。有人認為LOP是繼OOP之後的下一個重要的程式設計範式,我們不妨拭目以待。”

句号整理了一下頭緒:“能不能這麼說:如果處理一些複雜、非标準格式的文檔,可以考慮用元程式設計;如果整個業務邏輯複雜多變,可以考慮利用現有的DSL或創造新的DSL來處理業務,即所謂的語言導向式程式設計。”

“總結得不錯,不過特定格式的文檔有了專門的解析器後,這種文檔格式标準就可視為一種語言了,不是嗎?這本質上就是DSL啊。”冒号出語點化。

句号頓時醒悟:“是啊,就像XML、HTML一樣,能被程式認識的格式可不就是一種計算機語言嘛。”

冒号将話題延伸:“我們的想象力可以再狂野些,在文本DSL的基礎上裹以圖形界面,進而引進圖形語言。如果再将部分業務邏輯開放給使用者定制,那麼你的客戶會欣喜地發現,他們的經理隻要點點滑鼠就可以改變整個業務流程了,而這一切不僅不需要軟體開發方或第3方的參與,連本公司的技術人員也免了。這時候倒是你的老闆發愁了:你的設計太過完美,客戶的後續開發費怕是賺不到啰。”

衆人一樂。

問号繼續發問:“還有其他元程式設計的應用嗎?”

冒号随口舉了幾例:“元程式設計的例子比比皆是:許多IDE如Visual Studio、Delphi、Eclipse等均能通過向導、拖放控件等方式自動生成源碼;UML模組化工具将類圖轉換為代碼;Servlet引擎将JSP轉換為Java代碼;包括Spring、Hibernate、XDoclet在内的許多架構和工具都能從配置檔案、annotation/attribute等中産生代碼。”

引号仍不知足:“這些應用雖然典型,但都是些開發工具、架構引擎之類的基礎軟體,有沒有平時程式設計就能用到的例子?”

“當然有!”冒号堅定地答複,“有時程式中會出現大量的重複代碼,卻囿于文法上的限制無法進一步抽象化和子產品化。如果采用手工編寫或單純拷貝的方法,既費時又易錯,顯為下策。有時可借助IDE内置的代碼生成功能,但一方面局限性很大,另一方面無法自動化和版本化。”

問号插問:“什麼叫版本化?”

冒号解釋:“理想情況下,一個程式員對程式的貢獻都應該儲存在版本控制系統(version control system)中,以便跟蹤、比較、改進、借鑒和再生成。在IDE下自動生成的代碼本身可以被記錄,但産生代碼時的行為卻不能被記錄,幾次簡單的滑鼠動作就能産生較大的代碼差别,使得版本比較的意義大打折扣。順便說一句,離開IDE就無法編寫、編譯或調試的程式員,如同卸盔下馬後便失去戰鬥力的武士,是殘缺和孱弱的。”

問号有些明白了:“這是因為滑鼠行為本身在代碼中是沒有痕迹的。”

“不僅是滑鼠行為,有些需要鍵盤互動的行為也是沒有痕迹的。比如在指令行下用debugger來調試的行為無法被記錄,也難以重複和自動化,隻能作為權宜之策。相比之下,日志(logging)和單元測試(unit test)具有明顯的優勢[2]。”冒号答完,立馬重返主題,“回到上面的問題,既然有重複的代碼,不能從文法上提煉,不妨退一步從文字上提煉。我們可以利用AWK、Perl之類的擅長文字處理的腳本語言,當然也可以用Java、C等非腳本語言,再輔以XSLT之類的模闆語言,自動生成重複代碼。這樣不僅靈活性強,而且生成代碼的代碼——也就是元程式代碼可以被重用,元程式的資料來源也能版本化。”

句号深得要領:“就像Hibernate中的antlr包一樣,真正的源碼反而不在版本控制中了。一方面沒有儲存的必要——可以自動生成;另一方面沒有比較的必要——元程式的資料來源的變化比實際源碼的變化更簡明、更直覺。”

冒号繼續推進:“另外,有時程式的結構需要動态改變,而C++、Java、C#等靜态語言是不允許動态變更類的成員或實作代碼的,利用元程式設計便可突破這種限制。”

逗号恍然大悟:“原來元程式設計就是編寫能自動生成源代碼的程式。”

“也不盡然。”冒号馬上修正道,“自動生成源代碼的程式設計也屬于另一種程式設計範式——産生式程式設計(Generative Programming)[3]的範疇。差別在于後者更看重代碼的生成,而元程式設計看重的是生成代碼的可執行性。另外,除了在編譯期間生成源代碼的靜态元程式設計,還有能在運作期間修改程式的動态元程式設計。從低級的彙編語言到一些進階的動态語言如Perl、Python、Ruby、JavaScript、Lisp、Prolog等均支援此類功能。比如,許多腳本語言都提供eval函數,可以在運作時将字元串作為表達式來運算[4]。”

問号突然問道:“編寫病毒算不算元程式設計?”

“編寫一個隻是删除或感染檔案的病毒,不必用到元程式設計。但如果要開發一個能自我變異的智能病毒,那就需要元程式設計了。不過你要是把元程式設計用在這方面,可别說是我教的。”冒号開了個玩笑。

引号自言自語:“程式的程式,就是程式的平方。”

“也可以是程式的立方,4次方……理論上是無限次方。在傳統的程式設計中,運算是動态的,但程式本身是靜态的;在元程式設計中,二者都是動态的。元程式将程式作為資料來對待,能自我發現、自我賦權和自我更新,有着其他程式所不具備的自覺性、自适應性和智能性,可以說是一種最進階的程式。它要求程式設計者超越正常的程式設計思維,在一種嶄新的高度上了解程式設計。想象一下吧!”冒号激情勃發,“如果有一天機器人能自我學習、自我完善,甚至能生産新的機器人,實作‘智能繁衍’,是不是很美妙?”

“我怎麼覺得特恐怖呢?豈止是程式員,所有地球人的飯碗都會被它們砸光了。”歎号此言一出,衆皆忍俊不禁。

總結

元程式設計是編寫、操縱程式的程式。在傳統的程式設計中,運算是動态的,但程式本身是靜态的;在元程式設計中,二者都是動态的。

元程式設計能減少手工程式設計,突破原語言的文法限制,提升語言的抽象級别與靈活性,進而提高程式員的生産效率。

元程式設計有諸多應用:許多開發工具、架構引擎之類的基礎軟體都有自動生成源代碼的功能;創造DSL以便更高效地處理專門領域的業務;自動生成重複代碼;動态改變程式的語句、函數,類,等等。

IDE下自動生成的代碼通常局限性大且可讀性差,小操作可能造成的源碼上的大差異,削弱了版本控制的意義。用自編的無需人機互動的元程式來生成代碼,隻須将元程式的資料來源版本化,簡明而直覺。同時由于元程式可以随時修改,是以局限性小,更加靈活。

語言導向式程式設計(LOP)通過建立一套專用語言DSL來編寫程式。相比通用語言,DSL更簡單、更抽象、更專業、更接近自然語言和聲明式語言、開發效率更高,同時有助于專業程式員與業務分析員之間的合作。

語言導向式程式設計一般通過元程式設計将專用語言轉化為通用語言。

産生式程式設計與靜态元程式設計都能自動生成源代碼。産生式程式設計強調代碼的生成,元程式設計強調生成代碼的可執行性。此外,動态元程式設計并不生成源代碼,但能在運作期間修改程式。

元程式将程式作為資料來對待,有着其他程式所不具備的自覺性、自适應性和智能性,可以說是一種最進階的程式。

參考

[1]  Martin Ward.Language Oriented Programming.

               http://www.cse.dmu.ac.uk/~mward/martin/papers/middle-out-t.pdf

[2]  Sergey Dmitriev.Language Oriented Programming: The Next Programming Paradigm.

               http://www.onboard.jetbrains.com/is1/articles/04/10/lop/mps.pdf

[3]  Wikipedia.Metaprogramming.

         http://en.wikipedia.org/wiki/Metaprogramming

插語

[1]Martin Ward最早提出此範式,見參考文獻[1]。

[2]雖然調試與日志和測試不是一碼事,但合理的日志和單元測試能大量減少調試工作。

[3]也譯作“生成式程式設計”,屬于自動程式設計(Automatic Programming)範疇。

[4]考慮到eval過于廣泛和強大,有些動态語言還提供其他更明确和更安全的元程式設計機制,如JavaScript可用字元串來建構Function,Ruby更是提供了define_method、instance_eval、 class_eval和module_eval等諸多元程式設計方法。

           歡迎轉載,轉載時請注明:

本文出自電子工業出版社博文視點(武漢)新書《冒号課堂——程式設計範式與OOP思想》。

 http://www.china-pub.com/196068&ref=ps

 http://www.douban.com/subject/4031906/

繼續閱讀