编程语言的发展趋势及未来方向(2):声明式编程与DSL
?
這里先從聲明式(Declarative)編程談起。
?
目前我們在編寫軟件時大量使用的是命令式(Imperative)編程語言,例如C#,Java或是C++等等。這些語言的特征在于,寫出的代碼除了表現出“什么(What)”是你想做的事情之外,更多的代碼則表現出實現的細節,也就是“如何(How)”完成工作。這部分代碼有時候多到掩蓋了我們原來問題的解決方案。比如,你會在代碼里寫for循環,if語句,a等于b,i加一等等,這體現出機器是如何處理數據。首先,這種做法讓代碼變得冗余,而且它也很難讓執行代碼的基礎設施更聰明地判斷該如何去執行代碼。當你寫出這樣的命令是代碼,然后把編譯后的中間語言交給虛擬機去執行,此時虛擬機并沒有多少空間可以影響代碼的執行方式,它只能根據指令一條一條老老實實地去執行。例如,我們現在想要并行地執行程序就很困難了,因為更高層次的一些信息已經丟失了。這樣,我們只能在代碼里給出“How”,而不能體現出“What”的信息。
有多種方式可以將“What”轉化為更為“聲明式”的編程風格,我們只要能夠在代碼中體現出更多“What”,而不是“How”的信息,這樣執行環境便可以更加聰明地去適應當前的執行要求。例如,它可以決定投入多少CPU進行計算,你的當前硬件是什么樣的,等等。
?
我之前提到過,現在有兩種比較重要的成果,一是DSL(Domain Specific Language,領域特定語言),另一個則是函數式編程。
其實DSL不是什么新鮮的玩意兒,我們平時一直在用類似的東西,比如,SQL,CSS,正則表達式,有的可能更加專注于一個方面,例如Mathematica,LOGO等等。這些語言的目標都是特定的領域,與之相對的則是GPPL(General Purpose Programming Language,通用目的編程語言)。
?
對于DSL而言其實并沒有一個明確的定義,在這里我也不打算為它下個定義,例如UML甚至根本沒有特定的語法。不過我這里會談一些我覺得比較重要的東西。
?
Martin Fowler提出DSL應該分為外部DSL及內部DSL兩種,我認為這種劃分方式還是比較有意義的。外部DSL是自我包含的語言,它們有自己特定語法、解析器和詞法分析器等等,它往往是一種小型的編程語言,甚至不會像GPPL那樣需要源文件。與之相對的則是內部DSL。內部DSL其實更像是種別稱,它代表一類特別API及使用模式。這里我會給你們看一些示例。
?
這些是我們平時會遇到的一些外部DSL,如這張幻燈片上表現的XSLT,SQL或是Unix腳本。外部DSL的特點是,你在構建這種DSL時,其實扮演的是編程語言設計者的角色,這個工作并不會交給普通人去做。外部DSL一般會直接針對特定的領域設計,而不考慮其他東西。James Gosling曾經說過這樣的話,每個配置文件最終都會變成一門編程語言。你一開始可能只會用它表示一點點東西,然后慢慢你便會想要一些規則,而這些規則則變成了表達式,可能你還會定義變量,進行條件判斷等等。而最終它就變成了一種奇怪的編程語言,這樣的情況屢見不鮮。
事實上,現在有一些公司也在關注DSL的開發。例如以前在微軟工作的Charles Simonyi提出了Intentional Programming的概念,還有一個叫做JetBrains的公司提供一個叫做MPS(Meta Programming System)的產品。最近微軟也提出了自己的Oslo項目,而在Eclipse世界里也有個叫做Xtext的東西,所以其實在這方面現在也有不少人在嘗試。
我在觀察外部DSL時,往往會關注它的語法到底提供了多少空間,例如一種XML的方言,利用XML方言的好處在于有不少現成的工具可用,這樣可以更快地定義自己的語法。
?
而內部DSL,正像我之前說的那樣,它其實只是一系列特別的API及使用模式的別稱。這里則是一些LINQ查詢語句,Ruby on Rails以及jQuery代碼。內部DSL的特點是,它其實只是一系列API,但是你可以“假裝”它們一種DSL。內部DSL往往會利用一些“流暢化”的技巧,例如像這里的LINQ或jQuery那樣把一些方法通過“點”連接起來。有些則利用了元編程的方式,如這里的Ruby on Rails就涉及到了一些元編程。這種DSL可以訪問語言中的代碼或變量,以及利用如代碼補全,重構等母語言的所有特性。
?
現在我會花幾分鐘時間演示一下我所創建的DSL,也就是LINQ。我相信你們也已經用過不少LINQ了,不過這里我還是快速的展示一下我所表達的更為“聲明式”的編程方式。
public class Product{ public int ProductID { get; set; } public string ProductName { get; set; } public string CategoryName { get; set; } public int UnitPrice { get; set; } public static List<Product> GetProducts() { /* ... */ } }public partial class _Default : System.Web.UI.Page{ protected void Page_Load(object sender, EventArgs e){ List<Product> products = Product.GetProducts(); List<Product> result = new List<Product>(); foreach (Product p in products){ if (p.UnitPrice > 20) result.Add(p);}GridView1.DataSource = result;GridView1.DataBind();} }這里有許多Product對象,那么現在我要篩選出所有單價大于20的那些, 再把他們顯示在一個GridView中。傳統的做法就是這樣,我先得到所有的Product對象,然后foreach遍歷每個對象,再判斷每個對象的單價,最終把數據綁定到GridView里。運行這個程序……(打開頁面)這就是就能得到結果。
好,那么現在我要做一些稍微復雜的事情。可能我不是要展示單價超過20的Product對象,而是要查看每個分類中究竟有多少個單價超過20的對象,然后根據數量進行排序。如果不用DSL完成這個工作,那么我可能會先定義一個對象來表示結果:
class Grouping{ public string CategoryName { get; set; } public int ProductCount { get; set; } }這是個表示分組的對象,用于保存分類的名稱和產品數量。然后我們就會寫一些十分丑陋的代碼:
Dictionary<string, Grouping> groups = new Dictionary<string, Grouping>();foreach (Product p in products) { if (p.UnitPrice >= 20){ if (!groups.ContainsKey(p.CategoryName)){ Grouping r = new Grouping();r.CategoryName = p.CategoryName;r.ProductCount = 0;groups[p.CategoryName] = r;}groups[p.CategoryName].ProductCount++;} }List<Grouping> result = new List<Grouping>(groups.Values); result.Sort(delegate(Grouping x, Grouping y) { return ? ? ? ?x.ProductCount > y.ProductCount ? -1 :x.ProductCount < y.ProductCount ? 1 :0; });我先創建一個新的字典,用于保存分類名稱到分組的對應關系。然后我遍歷每個Product對象,對于每個單價大于20的對象,如果字典中還沒有保存對應的分組則創建一個,然后將數量加一。然后為了排序,我調用Sort方法,于是我要提供一個委托作為排序方法,然后blablablabla……執行之后……(打開頁面)我自然可以得到想要的結果。
但是,首先這些代碼寫起來需要花費一些時間,很顯然。然后仔細觀察,你會發現這寫代碼幾乎都是在表示“How”,而“What”基本已經丟失了。假設我離開了,現在新來了一個程序員要維護這段代碼,他會需要一點時間才能完整理解這段代碼,因為他無法直接看清代碼的目標。
不過如果這里我們使用DSL,也就是LINQ,就像這樣:
var result = products.Where(p => p.UnitPrice >= 20).GroupBy(p => p.CategoryName).OrderByDescending(g => g.Count()).Select(g => new { CategoryName = g.Key, ProductCount = g.Count() });products……先調用Where……blablabla……再GroupBy等等。由于我們這里可以使用DSL來表示高階的術語,用以體現我們想做的事情。于是這段代碼則更加關注于“What”而不是“How”。我這里不會明確地指示我想要過濾的方式,我也不會明確地說我要建立字典和分類,這樣基礎結構就可以聰明地,或者說更加聰明地去確定具體的執行方式。你可能比較容易想到我們可以并行地執行這段代碼,因為我沒有顯式地指定做事方式,我只是表示出我的意圖。
我們打開頁面……(打開頁面)很顯然我們得到了相同的結果。
這里比較有趣的是,內部DSL是如何設計進C#語法中的,為此我們為C# 3.0添加了一系列的特性,例如Lambda表達式,擴展方法,類型推斷等等。這些特性統一起來之后,我們就可以設計出更為豐富的API,組合之后便成為一種內部DSL,就像這里的LINQ查詢語言。
除了使用API的形式之外,我們還可以這樣做:
var result = from p in products where p.UnitPrice >= 20 group p by p.CategoryName into g orderby g.Count() descendingselect new { CategoryName = g.Key, ProductCount = g.Count() };編譯器會簡單地將這種形式轉化為前一種形式。不過,這里我認為有意思的地方在于,你完全可以創建一門和領域編程語言完全無關的語法,然后等這種語法和API變得流行且豐富起來之后,再來創一種新的表現形式,就如這里的LINQ查詢語法。我頗為中意這種語言設計的交流方式。
OK,現在我們回到下面的內容。
原文鏈接:http://blog.zhaojie.me/2010/04/trends-and-future-directions-in-programming-languages-by-anders-2-declarative-programming-and-dsl.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的编程语言的发展趋势及未来方向(2):声明式编程与DSL的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 编程语言的发展趋势及未来方向(3):函数
- 下一篇: 编程语言的发展趋势及未来方向(1):历史
