這是Anders Hejlsberg(不用介紹這是誰了吧)在比利時TechDays 2010所作的開場演講。因爲最近我在博客上關於語言的討論比較多,出於應景,也打算將Anders的演講完整地聽寫出來。在上一部分中,Anders闡述了他眼中聲明式編程的理念及DSL,並演示C#中一種內部DSL的形式:LINQ。在這一部分中,Anders談及了聲明式編程的另外一個重要組成部分:函數式編程,並使用.NET平臺上的函數式編程語言F#進行了演示。html
若是沒有特別說明,全部的文字都直接翻譯自Anders的演講,並使用我本身的口語習慣表達出來,對於Anders的口誤及反覆等狀況,必要時在譯文中天然也會進行忽略。爲了方便理解,我也會將視頻中關鍵部分進行截圖,而某些代碼演示則會直接做爲文章內容發表。程序員
(聽寫開始,接上篇)編程
關於聲明式編程的還有一部分重要的內容,那即是函數式編程。函數式編程已經有很長時間的歷史了,當年LISP即是個函數式編程語言。除了LISP之外咱們還有其餘許多函數式編程語言,如APL、Haskell、Scheme、ML等等。關於函數式編程在學術界已經有過許多研究了,在大約5到10年前許多人開始吸取和整理這些研究內容,想要把它們融入更爲通用的編程語言。如今的編程語言,如C#、Python、Ruby、Scala等等,它們都受到了函數式編程語言的影響。安全
我想在這裏先花幾分鐘時間簡單介紹一下我眼中的函數式編程語言。我發現不少人據說過函數式編程語言,但還不十分清楚它們和普通的命令式編程語言究竟有什麼區別。現在咱們在使用命令式編程語言寫程序時,咱們常常會寫這樣的語句,嗨,x等於x加一,此時咱們大量依賴的是狀態,可變的狀態,或者說變量,它們的值能夠隨程序運行而改變。數據結構
可變狀態很是強大,但隨之而來的即是叫作「反作用」的問題。在使用可變狀態時,你的程序則會包含反作用,好比你會寫一個無需參數的void方法,而後它會根據你的調用次數或是在哪一個線程上進行調用對程序產生影響,由於void方法會改變程序內部的狀態,從而影響以後的運行效果。框架
而在函數式編程中則不會出現這個狀況,由於全部的狀態都是不可變的。你能夠聲明一個狀態,可是不能改變這個狀態。並且因爲你沒法改變它,因此在函數式編程中不須要變量。事實上對函數式編程的討論更像是數學、公式,而不像是程序語句。若是你把x = x + 1這句話交給一個程序員看,他會說「啊,你在增長x的值」,而若是你把它交給一個數學家看,他會說「嗯,我知道這不是true」。異步
然而,若是你給他看這條語言,他會說「啊,y等於x加一,就是把x + 1的計算結果交給y,你是爲這個計算指定了一個名字」。這時候在思考時就是另外一種方式了,這裏y不是一個變量,它只是x + 1的名稱,它不會改變,永遠表明了x + 1。編程語言
因此在函數式編程語言中,當你寫了一個函數,接受一些參數,那麼當你調用這個函數時,影響函數調用的只是你傳進去的參數,而你獲得的也只是計算結果。在一個純函數式編程語言中,函數在計算時不會對進行一些神奇的改變,它只會使用你給它的參數,而後返回結果。在函數式編程語言中,一個void方法是沒有意義的,它惟一的做用只是讓你的CPU發熱,而不能給你任何東西,也不會有反作用。固然如今你可能會說,這個CPU發多少熱也是一個反作用,好吧,不過咱們如今先不討論這個問題。函數式編程
這裏的關鍵在於,你解決問題的方法和之前大不同了。我這裏仍是用代碼來講明問題。使用函數式語言寫沒有反作用的代碼,就比如在Java或C#中使用final或是readonly的成員。函數
例如這裏,咱們有一個Point類,構造函數接受x和y,還有一個MoveBy方法,能夠把一個點移動一些位置。 在傳統的命令式編程中,咱們會改變Point實例的狀態,這麼作在平時可能不會有什麼問題。可是,若是我把一個Point對象同時交給3個API使用,而後我修改了Point,那麼如何才能告訴它們狀態改變了呢?可能咱們可使用事件,blablabla,若是咱們沒有事件,那麼就會出現那些不愉快的反作用了。
那麼使用函數式編程的形式寫代碼,你的Point類仍是能夠包含狀態,例如x和y,不過它們是readonly的,一旦初始化之後就不能改變了。MoveBy方法不能改變Point對象,它只能建立一個新的Point對象並返回出來。這就是一個建立新Point對象的函數,不是嗎?這樣就可讓調用者來決定是使用新的仍是舊的Point對象,但這裏不會有產生反作用的狀況出現。
在函數式編程裏天然不會只有Point對象,例如咱們會有集合,如Dictionary,Map,List等等,它們都是不可變的。在函數式編程中,當咱們向一個List裏添加元素時,咱們會獲得一個新的List,它包含了新增的元素,但以前的List依然存在。因此這些數據結構的實現方式是有根本性區別的,它們的內部結構會設法讓這類操做變的儘量高效。
在函數式編程中訪問狀態是十分安全的,由於狀態不會改變,我能夠把一個Point或List對象交給任意多的地方去訪問,徹底不用擔憂反作用。函數式編程的十分容易並行,由於我在運行時不會修改狀態,所以不管多少線程在運行時均可以觀察到正確的狀態。兩個函數徹底無關,所以它們是並行仍是順序地執行便沒有什麼區別了。咱們還能夠有延遲計算,能夠進行Memorization,這些都是函數式編程中十分有趣的方面。
你可能會說,那麼咱們爲何不都用這種方法來寫程序呢?嗯,最終,就像我以前說的那樣,咱們不能只讓CPU發熱,咱們必需要把計算結果表現出來。那麼咱們在屏幕上打印內容時,或者把數據寫入文件或是Socket時,其實就產生了反作用。所以真實世界中的函數式編程,每每都是把純粹的部分進行隔離,或是進行更細緻的控制。事實上也不會有真正純粹的函數式編程語言,它們都會帶來必定的反作用或是命令式編程的能力。可是,它們默認是函數式的,例如在函數式編程語言中,全部東西默認都是不可變的,你必須作些額外的事情才能使用可變狀態或是產生危險的反作用。此時你的編程觀念便會有所不一樣了。
咱們在本身的環境中開發出了這樣一個函數式編程語言,F#,已經包含在VS 2010中了。F#誕生於微軟劍橋研究院,由Don Syme提出,他在F#上已經工做了5到10年了。F#使用了另外一個函數式編程語言OCaml的常見核心部分,所以它是一個強類型語言,並支持一些如模式匹配,類型推斷等現代函數式編程語言的特性。在此之上,F#又增長了異步工做流,度量單位等較爲前沿的語言功能。
而F#最爲重要的一點多是,在我看來,它是第一個和工業級的框架和工具集,如.NET和Visual Studio,有深刻集成的函數式編程語言。F#容許你使用整個.NET框架,它和C#也有相似的執行期特徵,例如強類型,並且都會生成高效的代碼等等。我想,如今應該是展現一些F#代碼的時候了。
首先我想先從F#中我最喜歡的特性講起,這是個F#命令行……(打開命令行窗口以及一個F#源文件)……F#包含了一個交互式的命令行,這容許你直接輸入代碼並執行。例如輸入5……x等於5……而後x……顯示出x的值是5。而後讓sqr x等於x乘以x,因而我這裏定義了一個簡單的函數,名爲sqr。因而咱們就能夠計算sqr 5等於25,sqr 10等於100。
F#的使用方式十分動態,但事實上它是一個強類型的編程語言。咱們再來看看這裏。這裏我定義了一個計算平方和的函數sumSquares,它會遍歷每一個列表中每一個元素,平方後再把它們相加。讓我先用命令式的方式編寫這個函數,再使用函數式的方式,這樣你能夠看出其中的區別。
let sumSquaresI l = let mutable acc = 0 for x in l do acc <- acc + sqr x acc
這裏先是命令式的代碼,咱們先建立一個累加器acc爲0,而後遍歷列表l,把平方加到acc中,而後最後我返回acc。有幾件事情值得注意,首先爲了建立一個可變的狀態,我必須顯式地使用mutable進行聲明,在默認狀況下這是不可變的。
還有一點,這段代碼裏我沒有提供任何的類型信息。當我把鼠標停留在方法上時,就會顯示sumSquaresI方法接受一個int序列做爲參數並返回一個int。你可能會想int是哪裏來的,嗯,它是由類型推斷而來的。編譯器從這裏的0發現acc必須是一個int,因而它發現這裏的加號表示兩個int的相加,因而sqr函數返回的是個int,再接下來blablabla……最終它發現這裏處處都是int。
若是我把這裏修改成浮點數0.0,鼠標再停留一下,你就會發現這個函數接受和返回的類型都變成float了。因此這裏的類型推斷功能十分強大,也十分方便。
如今我能夠選擇這個函數,讓它在命令行裏執行,而後調用sumSquaresI,提供1到100的序列,就能獲得結果了。
let rec sumSquaresF l = match l with | [] -> 0 | h :: t -> sqr h + sumSquaresF t
那麼如今咱們來換一種函數式的風格。這裏是另外一種寫法,能夠說是純函數式的實現方式。若是你去理解這段代碼,你會發現有很多數學的感受。這裏我定義了sumSqauresF函數,輸入一個l列表,而後使用下面的模式去匹配l。若是它爲空,則結果爲0,不然把列表匹配爲頭部和尾部,而後便將頭部的平方和尾部的平方和相加。
你會發現,在計算時我不會去改變任何一個變量的值,我只是建立新的值。我這裏會使用遞歸,就像在數學裏咱們常用遞歸,把一個公式分解成幾個變化的形式,以此進行遞歸的定義。在編程時咱們也使用遞歸的作法,而後編譯器會設法幫咱們轉化成尾遞歸或是循環等等。
因而咱們即可以執行sumSquaresF函數,也能夠獲得相同的結果。固然實際上可能你並不會像以前這樣寫代碼,你可能會使用高階函數:
let sumSquares l = Seq.sum (Seq.map (fun x -> x * x) l )
例如這裏,我只是把函數x乘以x映射到列表上,而後相加。這樣也能夠獲得相同的結果,並且這多是更典型的作法。我這裏只是想說明,這個語言在編程時可能會給你帶來徹底不一樣的感覺,雖然它的執行期特徵和C#比較接近。
這即是關於F#的內容。