深入解讀Ruby DSL概念
對于一個初學(xué)Ruby語言的朋友來說,對于Ruby DSL的理解還不是很清晰。在這篇文章中我們將會為大家詳細(xì)介紹有關(guān)Ruby DSL的一些使用方法。#t#
DSL是一種專注于某一特定領(lǐng)域的語言,使用通用語言(如C或者Java)當(dāng)然可以得到與DSL相同的功能。但是這樣會產(chǎn)生大量繁瑣的代碼并導(dǎo)致大量的領(lǐng)域知識被隱藏在通用語言構(gòu)造中(如for循環(huán),if條件,方法調(diào)用,import聲明等等)。
生成以及維護(hù)通用語言編寫的代碼本身也是問題所在:專家們必須將他們的知識變?yōu)榇a。通常這些專家(銷售人員,經(jīng)理或者園?。┤鄙倬幊讨R,這意味著他們必須與程序員進(jìn)行協(xié)作。當(dāng)然,這也意味對代碼的任何一次修改都包括許多繁瑣的步驟:如果一個領(lǐng)域?qū)<倚枰淖兡承┕δ?,她必須首先與程序員進(jìn)行交流,由程序員實現(xiàn)這些修改,然后領(lǐng)域?qū)<覚z查這些代碼是否表現(xiàn)出期望的行為,諸如此類。
對于上述問題,其中一個可能的解決方案是開發(fā)一種適應(yīng)特定環(huán)境的語言,非技術(shù)人員可以用它來解決問題。這種語言較通用語言更簡潔并且僅僅提供目標(biāo)領(lǐng)域所需要的類型以及特定的語言構(gòu)造。
在訪談中,Obie將DSL類比為自然語言中的俚語或者行話。全世界喜愛咖啡的人對下面這句話一定非常熟悉:
Venti half-caf, non-fat, no foam, no whip latte
在正常的對話中,上述語言不能傳遞正確的語義。并且世界上大多數(shù)的咖啡館里可能僅僅提供最普通的牛奶加咖啡。但是在合適的場所使用上述語言(例如,星巴克),它將讓你使用最少的詞語喝到想要的飲料并且很少會造成誤解。
有多種方式可以實現(xiàn)DSL。其中一個選擇是定義一種語法并使用解析器生成工具(如ANTLR或者YACC)來生成解析器。通過它可以將Ruby DSL代碼轉(zhuǎn)化為某種可以被解釋的數(shù)據(jù)結(jié)構(gòu)(語法樹)。其中一個例子是Make文件,它被用來定義構(gòu)建過程(編譯,打包,部署)。
另一種方式是使用XML而不是解析器,它將對于解析器生成工具的依賴變?yōu)閷τ赬ML解析器的依賴,有許多工具可以用來支撐XML處理(使用DOM解析器可以得到類似語法樹的數(shù)據(jù)結(jié)構(gòu),同時使用XPATH來提取數(shù)據(jù),等等)。Ant,是一個基于XML的DSL, 構(gòu)建過程被定義為XML格式。Make和Ant就是外部DSL最典型的實現(xiàn)。
另一個解決方案是內(nèi)部Ruby DSL,它不需要使用任何解析器生成工具以及XML解析器,取而代之的是使用現(xiàn)有的合法的通用語言的構(gòu)造,很明顯,這種語言的構(gòu)造必須非常靈活才可以容納各種簡練的DSL代碼。
內(nèi)部DSL的***解決工具非LISP以及類LISP語言莫屬,原因是LISP靈活的語法,它可以被概括為:
原子
幾乎任何字符都可以成為原子,如foo,:::bar:::甚至加號都是合法的原子
零個或多個原子組成的序列
放入圓括號中
LISP宏特性使得開發(fā)者可以很輕易的在LISP中定義DSL并且將其解釋或者展開為可以執(zhí)行的通常的LISP代碼。
另外一種適于使用內(nèi)部DSL的語言是Ruby, 沒什么可驚奇的,想想Ruby豐富的語法吧,它是Ruby得以支持內(nèi)部Ruby DSL的部分特性。下面是Obie在PPT上所使用到的觀點以及相應(yīng)的示例代碼:
方法調(diào)用中括號可以省略。這看起來沒什么大不了的, 但是它使得下面的聲明成為合法的Ruby代碼:
order = latte venti, half_caf, non_fat, no_foam, no_whip在上面的例子中,latte(拿鐵咖啡)以及我們所需要的特別的口味兒都是方法調(diào)用的一部分。latte方法返回一個將咖啡各個屬性進(jìn)行了初始化的對象。
類的定義在載入時可以被動態(tài)執(zhí)行
class RuleSet < ActiveRecord::Base has_many :commends, :dependend => :delete_all # ... more... end這是一個內(nèi)部DSL在Rails的ActiveRecord中的例子。has_many調(diào)用會在類***次被載入時執(zhí)行。這個調(diào)用被用來設(shè)定類的關(guān)聯(lián)以及行為,例如,它可以通過define_method調(diào)用向類添加一些方法。這樣這段Ruby DSL代碼的用戶可以使用非常簡潔,描述性的方式來定義類某些方面的行為。事實上,這與LISP宏有很多相似的地方,他們都在代碼被載入時進(jìn)行工作。
簡潔的代碼塊書寫格式(Block)
塊是自包含的Ruby代碼。它可以被保存,當(dāng)作參數(shù)傳遞并在以后執(zhí)行,這個概念也被稱做是匿名方法,lamda演算或者閉包(Closures),在Ruby中向方法傳遞塊非常簡潔緊湊,它使得實現(xiàn)特定語言構(gòu)造變得非常容易,Rake是與Make和Ant非常相似的構(gòu)建工具,我們來看一個例子:
task :default => [:test] task :test do ruby "test/unittest.rb" end上面的類使用了我們在這里提到的所有的三種概念。task是一種方法調(diào)用,但是沒有圓括號讓它變的更具描述性。task調(diào)用在載入時被執(zhí)行,對內(nèi)部數(shù)據(jù)結(jié)構(gòu)進(jìn)行設(shè)置。test任務(wù)的邏輯在塊中進(jìn)行定義(在do和end之間的代碼),并在適當(dāng)?shù)臅r候被執(zhí)行。
在Ruby中,內(nèi)部DSL使得編寫簡潔和描述性的規(guī)范變得非常容易。正如我們從has_many例子中看到的,內(nèi)部Ruby DSL也可以很容易的與通常命令式的Ruby代碼進(jìn)行混合。