天天看點

對C#之父Anders Hejlsberg演講的總結

——基于對C#之父Anders Hejlsberg演講的總結

文 / 趙劼

程式設計離不開程式設計語言,但是程式設計語言在國内的大環境中似乎一直是個二等公民。國内的計算機教育和工程教育訓練,似乎一直在宣傳“語言不重要,重要的是思想”、“語言一通百通”等觀點,甚至在許多人眼中,語言的讨論完全是不入流的,但其實程式設計語言與工具、架構或開發方法等一樣,都對生産力有着重要的影響。事實上,語言的發展曆史比其他方面更為悠久,并且在過去十幾年,甚至最近幾年中都依然在不斷的碰撞和演變。期間一些新的語言誕生了,而另一些在當時看來陽春白雪的語言和程式設計範式也重新獲得了重視。

Anders Hejlsberg是微軟的Technical Fellow,擔任C#程式設計語言的首席架構師,也參與了.NET Framework、VB.NET和F#等語言的設計與開發。幾個月前,Anders在比利時TechDays 2010及荷蘭DevDays 2010分别作了一場演講,闡述了他眼中的程式設計語言的發展趨勢及未來方向,本文便對他的觀點進行了總結。

大約25~30年前,Anders開發了著名的Turbo Pascal,這是一套集語言、編譯器及開發工具于一體的産品,也是Anders進入程式設計語言領域的起點。Anders談到,當年Turbo Pascal所用的Z-80和如今的計算機已經不可同日而語。與那時相比,如今的機器已經有大約10萬倍的外部存儲容量,1萬倍的記憶體大小,CPU速度也有大約1000倍的提高。但是,如果我們比較如今的Java代碼及當年的Pascal代碼,會發現它們的差别其實并不大。Anders認為程式設計語言的發展非常緩慢,期間當然出現了一些東西,例如面向對象等,但是遠沒有好上1000倍。事實上,近幾十年來的努力主要展現在架構及工具等方面(如圖1)。例如.NET Framework裡有超過一萬個類和十萬個方法,與Turbo Pascal相比的确有了超過1000倍的增長。類似的,現在的IDE包含了無數強大的功能,例如文法提示、重構、調試器等。與此相比,程式設計語言的改進的确很不明顯。

對C#之父Anders Hejlsberg演講的總結

圖1 近幾十年來語言、架構及工具的發展

在過去50~60年的程式設計曆史中,程式設計語言的抽象級别不斷提高,人們都在努力讓程式設計語言更有表現力,這樣就可以用更少的代碼完成更多的工作。我們一開始使用彙編,然後使用面向過程的語言(如Pascal和C),然後是面向對象語言(如C++),随後便進入了托管時代,語言運作于受托管的執行環境上(如C#和Java),它們的主要特性有自動垃圾收集、類型安全等。Anders認為這樣的趨勢還會繼續下去,還會有抽象級别越來越高的語言。另一方面,程式設計語言往往都傾向于建構于現有的工具上,而不會從頭寫起。現在出現的程式設計語言,例如F#、Scala和Clojure等,都是基于現有架構建構的,每次從頭開始的代價實在太高。

在Anders眼中,如今影響力較大的趨勢主要有三個(如圖2),分别是聲明式的程式設計風格(包括領域特定語言、函數式程式設計)、動态語言(最重要的方面是元程式設計能力)以及多核環境下的并發程式設計。此外随着語言的發展,原本常用的面向對象語言、動态語言或是函數式等邊界也變得越來越模糊,例如各種主要的程式設計語言都受到函數式語言的影響。是以,多範式程式設計語言也是一個愈發明顯的趨勢。

對C#之父Anders Hejlsberg演講的總結

圖2 影響力較大的三個趨勢

聲明式程式設計與DSL

目前常見的程式設計語言大都是指令式(Imperative)的,例如C#、Java或C++等。這些語言的特征在于,代碼裡不僅表現了“做什麼(What)”,而更多表現出“如何(How)完成工作”這樣的實作細節,例如for循環、i += 1等,甚至這部分細節會掩蓋我們的最終目标。在Anders看來,指令式程式設計通常會讓代碼變得十分備援,更重要的是由于它提供了過于具體的指令,這樣執行代碼的基礎設施(如CLR或JVM)沒有太多發揮空間,隻能老老實實地根據指令一步步地向目标前進。例如,并行執行程式會變得十分困難,因為像“執行目的”這樣更高層次的資訊已經丢失了。是以,程式設計語言的趨勢之一,便是能讓代碼包含更多的“What”,而不是“How”,這樣執行環境便可以更加聰明地去适應目前的執行要求。

關于聲明式的程式設計風格,Anders主要提出了兩個方面,第一個方面是DSL(Domain Specific Language,領域特定語言)。DSL不是什麼新鮮的玩意兒,我們平時經常接觸的SQL、CSS、正規表達式等都屬于DSL。有的DSL可能更加專注于一個方面,例如Mathematica、LOGO等。這些語言的目标都是特定的領域,與之相對的則是GPPL(General Purpose Programming Language,通用目的程式設計語言)。Martin Fowler将DSL分為外部DSL和内部DSL兩種。外部DSL有自己的特定文法、解析器和詞法分析器等,它們往往是一種小型的程式設計語言,甚至不會像GPPL那樣需要源檔案。與之相對的則是内部DSL。内部DSL其實更像是種别稱,它代表一類特别API及使用模式。

XSLT、SQL等都可以算作是外部DSL。外部DSL一般會直接針對特定的領域設計,而不考慮其他方面。James Gosling曾經說過:每個配置檔案最終都會變成一門程式設計語言。一開始你可能隻會用它表示一點點東西,慢慢地你便會想要一些規則,而這些規則則變成了表達式,後來你可能還會定義變量,進行條件判斷等,而最終它就變成了一種奇怪的程式設計語言。這樣的情況屢見不鮮。現在有一些公司也在關注DSL的開發。例如以前在微軟工作的Charles Simonyi提出了Intentional Programming的概念,還有JetBrains公司提供了叫做MPS(Meta Programming System)的産品。最近微軟也提出了自己的Oslo項目,而在Eclipse世界裡也有Xtext,是以如今在這方面已經有不少嘗試。由于外部DSL的獨立性,在某些情況下也會出現特定的工具,輔助領域專家或是開發人員編寫DSL代碼。還有一些DSL會以XML方言的形式提出,利用XML方言的好處在于有不少現成的工具可用,這樣可以更快地定義自己的文法。

内部DSL往往隻代表一系列特别的API及使用模式,例如LINQ查詢語句及Ruby on Rails中的Active Record聲明代碼等。内部DSL可以使用一系列API來“僞裝”成一種DSL,利用一些流暢化的技巧,例如像jQuery那樣把一些方法通過“點”連接配接起來,而另一些也會利用元程式設計的方式。内部DSL還有一些優勢,例如可以通路語言中的代碼或變量,以及利用代碼補全、重構等母語言的所有特性。

DSL的可讀性往往很高。例如,要篩選出單價大于20的産品,并對所屬種類進行分組,降序列出每組的分類名稱及産品數量。如果是用指令式的程式設計方式,可能是這樣的:

var groups = new Dictionary<string, Grouping>();

foreach (Product p in products)

{

if (p.UnitPrice >= 20)

{

if (!groups.ContainsKey(p.CategoryName))

{

Grouping g = new Grouping();

g.Name = p.CategoryName;

g.Count = 0;

groups[p.CategoryName] = g;

}

groups[p.CategoryName].ProductCount++;

}

}

var result = new List<Grouping>(groups.Values);

result.Sort(delegate(Grouping x, Grouping y)

{

return

x.Count > y.Count ? -1 :

x.Count < y.Count ? 1 :

0;

});

顯然這些代碼編寫起來需要一點時間,且很難直接看出它的真實目的,換言之,“What”幾乎完全被“How”所代替了。這樣,一個新的程式員必須花費一定時間才能了解這段代碼的目的。但如果使用LINQ,代碼便可以改寫成:

var result = products

.Where(p => p.UnitPrice >= 20)

.GroupBy(p => p.CategoryName)

.OrderByDescending(g => g.Count())

.Select(g => new { Name = g.Key, Count = g.Count() });

這段代碼更加關注的是“How”而不是“What”,它不會明确地給出過濾的操作方式,也沒有涉及到建立字典這樣的細節。這段代碼還可以利用C# 3.0中内置的DSL,即LINQ查詢語句來改寫:

var result =

from p in products

where p.UnitPrice >= 20

group p by p.CategoryName into g

orderby g.Count() descending

select new { Name = g.Key, Count = g.Count() };

編譯器會簡單地将LINQ差距語句轉化為前一種形式。這段代碼隻是表現出最終的目的,而不是明确指定做事的方式,這樣便可以很容易地并行執行這段代碼,如使用PINQ則幾乎不需要做出任何修改。

函數式程式設計

Anders提出的另一個重要的聲明式程式設計方式便是函數式程式設計。函數式程式設計曆史悠久,如當年的LISP便是函數式程式設計語言。除了LISP以外還有其他許多函數式程式設計語言,如APL、Haskell、ML等。函數式程式設計在學術界已經有過許多研究,大約在5~10年前許多人開始吸收和整理這些研究内容,想要把它們融入更為通用的程式設計語言。現在的程式設計語言,如C#、Python、Ruby、Scala等,都受到了函數式程式設計語言的影響。

使用指令式程式設計語言寫程式時,我們經常會編寫如x = x + 1這樣的語句,此時我們大量依賴的是可變狀态,或者說是變量,它們的值可以随程式運作而改變,可變狀态非常強大,但随之而來的便是“副作用”問題,例如一個無需參數的void方法,它會根據調用次數或是在哪個線程上進行調用對程式産生影響,它會改變程式内部的狀态,進而影響之後的運作效果。而在函數式程式設計中則不會出現這個情況,因為所有的狀态都是不可變的。事實上對函數式程式設計的讨論更像是數學、公式,而不是程式語句,如x = x + 1對于數學家來說,似乎隻是個永不為真的表達式而已。

函數式程式設計十分容易并行,因為它在運作時不會修改任何狀态,是以無論多少線程在運作時都可以觀察到正确的結果。假如兩個函數完全無關,那麼它們是并行還是順序執行便沒有什麼差別。當然,現實中的程式一定是有副作用的,例如向螢幕輸出内容,向Socket傳輸資料等,是以真實世界中的函數式程式設計往往都會考慮如何将有副作用的代碼分離出來。函數式程式設計預設是不可變的,開發人員必須做些額外的事情才能使用可變狀态或是危險的副作用,與之相反,C#或Java必須使用readonly或final來做到這一點。此時,使用函數式程式設計語言時的思維觀念便會有所不同。……(完整文章請關注08期雜志)