程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 冒號課堂§3.2:超級范式

冒號課堂§3.2:超級范式

編輯:關於JAVA

第三課 常用范式(2)

3.2 超級范式——提升語言的級別

智能繁衍:機器人生產機器人          ——題記

關鍵詞:編程范式,模板元編程,元編程,語言導向式編程,產生式編程

摘要:元編程簡談

?提問

什麼是元編程?它與通常的編程有何不同?

元編程有何用處?它有哪些應用?

相比自編的元程序,用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 << factorial<5>::value << endl; // 等價於 cout << 120 << endl;
}

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

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

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

歎號略一思索,便回答:“三大模塊:閱讀器讀出輸入文檔,解析器按照格式標准去解析,處理器對解析結果進行處理。”

“顯然關鍵在解析器,如果你是從頭做起,那麼問題至少有四。”冒號扳著指頭數:“第一、費時寫解析器代碼;第二、費時調試解析器代碼;第三、如果用戶更改格式標准,你得重復做上兩件事;第四、如果這段程序是大型程序的一部分,任何改動都可能意味著軟件的重新編譯、連接、測試、打包、部署等等。如果因為你的緣故公司不得不頻頻發布補丁包的話,你的飯碗恐怕是朝不保夕了。”

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

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

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

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

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

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

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

“你只說對了一半。”冒號不疾不緩地說,“DSL一般不會一步到位地編譯為第一代的機器語言或第二代的匯編語言,而是通過現成的編譯器生成器(compiler-compiler或compiler generator)首先轉化為第三代的高級語言。這樣不僅大大降低了難度,也方便了程序的調試。剛才提到的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中自動生成的,這些非人工編輯的文件是不會放在版本控制中的。”

眾人茅塞頓開。

句號想通了一個邏輯:“元編程作為超級范式的一個體現是,它能提升語言的級別。比如,有了編譯器的存在,匯編語言升級為第三代高級語言;同樣借助Yacc、ANTLR之類的元編程工具,第三代語言可以升級為第四代的DSL語言。”

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

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

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

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

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

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

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

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

眾人一樂。

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

冒號隨口舉了幾例:“元編程的例子比比皆是:許多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包一樣,真正的源碼反而不在版本控制中了。一方面沒有保存的必要——可以自動生成;另一方面沒有比較的必要——元程序的數據來源的變化比實際源碼的變化更簡明、更直觀。”

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

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

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

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

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

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

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

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

,插語

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

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

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

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

。總結

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

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

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

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

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

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

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

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved