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

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

 

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個。”冒號扳着指頭數,“第一,費時寫解析器代碼;第二,費時調試解析器代碼;第三,如果用戶更改格式標準,你得重複做上兩件事;第四,如果這段程序是大型程序的一部分,任何改動都可能意味着軟件的重新編譯、連接、測試、打包、部署,等等。如果因爲你的緣故公司不得不頻頻發佈補丁包的話,你的飯碗恐怕是朝不保夕了。”

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

“思路對頭!”冒號讚許道,“大家聽說過LexYacc嗎?它們能根據格式標準生成相應的解析器代碼。更妙的是,格式標準不限於靜態數據,甚至可以含有動態指令!這意味着用戶不僅能定義業務數據格式,還能定義業務流程。”

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

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

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

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

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

“你只說對了一半。”冒號不疾不緩地說,“DSL一般不會一步到位地編譯爲第1代的機器語言或第2代的彙編語言,而是通過現成的編譯器生成器(compiler-compilercompiler generator)首先轉化爲第3代的高級語言。這樣不僅大大降低了難度,也方便了程序的調試。剛纔提到的YaccYet Another Compiler Compiler)便是這樣的工具,能爲解析器(parser)產生C程序,多用於Unix下的編程。更現代的工具如ANTLR (ANother Tool for Language Recognition),能生成CC++JavaC#Python等多種語言的源程序。”

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

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

衆人茅塞頓開。

句號想通了一個邏輯:“元編程作爲超級範式的一個體現是,它能提升語言的級別。比如,有了編譯器的存在,彙編語言升級爲第3代高級語言;同樣藉助YaccANTLR之類的元編程工具,第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啊。”冒號出語點化。

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

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

衆人一樂。

問號繼續發問:“還有其他元編程的應用嗎?”

冒號隨口舉了幾例:“元編程的例子比比皆是:許多IDEVisual StudioDelphiEclipse等均能通過嚮導、拖放控件等方式自動生成源碼;UML建模工具將類圖轉換爲代碼;Servlet引擎將JSP轉換爲Java代碼;包括SpringHibernateXDoclet在內的許多框架和工具都能從配置文件、annotation/attribute等中產生代碼。”

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

“當然有!”冒號堅定地答覆,“有時程序中會出現大量的重複代碼,卻囿於語法上的限制無法進一步抽象化和模塊化。如果採用手工編寫或單純拷貝的方法,既費時又易錯,顯爲下策。有時可藉助IDE內置的代碼生成功能,但一方面侷限性很大,另一方面無法自動化版本化。”

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

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

問號有些明白了:“這是因爲鼠標行爲本身在代碼中是沒有痕跡的。”

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

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

冒號繼續推進:“另外,有時程序的結構需要動態改變,而C++JavaC#等靜態語言是不允許動態變更類的成員或實現代碼的,利用元編程便可突破這種限制。”

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

“也不盡然。”冒號馬上修正道,“自動生成源代碼的編程也屬於另一種編程範式—產生式編程(Generative Programming[3]的範疇。區別在於後者更看重代碼的生成,而元編程看重的是生成代碼的可執行性。另外,除了在編譯期間生成源代碼的靜態元編程,還有能在運行期間修改程序的動態元編程。從低級的彙編語言到一些高級的動態語言如PerlPythonRubyJavaScriptLispProlog等均支持此類功能。比如,許多腳本語言都提供eval函數,可以在運行時將字符串作爲表達式來運算[4]。”

問號突然問道:“編寫病毒算不算元編程?”

“編寫一個只是刪除或感染文件的病毒,不必用到元編程。但如果要開發一個能自我變異的智能病毒,那就需要元編程了。不過你要是把元編程用在這方面,可別說是我教的。”冒號開了個玩笑。

引號自言自語:“程序的程序,就是程序的平方。”

“也可以是程序的立方,4次方……理論上是無限次方。在傳統的編程中,運算是動態的,但程序本身是靜態的;在元編程中,二者都是動態的。元程序將程序作爲數據來對待,能自我發現、自我賦權和自我升級,有着其他程序所不具備的自覺性自適應性智能性,可以說是一種最高級的程序。它要求編程者超越常規的編程思維,在一種嶄新的高度上理解編程。想象一下吧!”冒號激情勃發,“如果有一天機器人能自我學習、自我完善,甚至能生產新的機器人,實現‘智能繁衍’,是不是很美妙?”

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

總結

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

元編程能減少手工編程,突破原語言的語法限制,提升語言的抽象級別與靈活性,從而提高程序員的生產效率。

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

IDE下自動生成的代碼通常侷限性大且可讀性差,小操作可能造成的源碼上的大差異,削弱了版本控制的意義。用自編的無需人機交互的元程序來生成代碼,只須將元程序的數據來源版本化,簡明而直觀。同時由於元程序可以隨時修改,因此侷限性小,更加靈活。

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

語言導向式編程一般通過元編程將專用語言轉化爲通用語言。

產生式編程與靜態元編程都能自動生成源代碼。產生式編程強調代碼的生成,元編程強調生成代碼的可執行性。此外,動態元編程並不生成源代碼,但能在運行期間修改程序。

元程序將程序作爲數據來對待,有着其他程序所不具備的自覺性、自適應性和智能性,可以說是一種最高級的程序。

參考

[1]  Martin WardLanguage Oriented Programming
               http://www.cse.dmu.ac.uk/~mward/martin/papers/middle-out-t.pdf

[2]  Sergey DmitrievLanguage Oriented Programming: The Next Programming Paradigm
               http://www.onboard.jetbrains.com/is1/articles/04/10/lop/mps.pdf

[3]  WikipediaMetaprogramming
         http://en.wikipedia.org/wiki/Metaprogramming

插語

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

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

[3]也譯作“生成式編程”,屬於自動編程Automatic Programming)範疇。

[4]考慮到eval過於廣泛和強大,有些動態語言還提供其他更明確和更安全的元編程機制,如JavaScript可用字符串來構建FunctionRuby更是提供了define_methodinstance_eval class_evalmodule_eval等諸多元編程方法。

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

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

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

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章