色婷婷AⅤ一区二区三区|亚洲精品第一国产综合亚AV|久久精品官方网视频|日本28视频香蕉

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 牛人業(yè)話 > 編程語言的發(fā)展趨勢及未來方向(3):函數(shù)式編程

          編程語言的發(fā)展趨勢及未來方向(3):函數(shù)式編程

          作者: 時間:2017-04-05 來源:網(wǎng)絡(luò) 收藏

            這是Anders Hejlsberg(不用介紹這是誰了吧)在比利時TechDays 2010所做的開場演講。由于最近我在博客上關(guān)于語言的討論比較多,出于應(yīng)景,也打算將Anders的演講完整地聽寫出來。在上一部分中,Anders闡述了他眼中聲明式編程的理念及DSL,并演示C#中一種內(nèi)部DSL的形式:LINQ。在這一部分中,Anders談及了聲明式編程的另一個重要組成部分:函數(shù)式編程,并使用.NET平臺上的函數(shù)式F#進(jìn)行了演示。

          本文引用地址:http://cafeforensic.com/article/201704/346198.htm

            如果沒有特別說明,所有的文字都直接翻譯自Anders的演講,并使用我自己的口語習(xí)慣表達(dá)出來,對于Anders的口誤及反復(fù)等情況,必要時在譯文中自然也會進(jìn)行忽略。為了方便理解,我也會將視頻中關(guān)鍵部分進(jìn)行截圖,而某些代碼演示則會直接作為文章內(nèi)容發(fā)表。

            (聽寫開始,接上篇)

              

           

            關(guān)于聲明式編程的還有一部分重要的內(nèi)容,那便是函數(shù)式編程。函數(shù)式編程已經(jīng)有很長時間的歷史了,當(dāng)年LISP便是個函數(shù)式。除了LISP以外我們還有其他許多函數(shù)式,如APL、Haskell、Scheme、ML等等。關(guān)于函數(shù)式編程在學(xué)術(shù)界已經(jīng)有過許多研究了,在大約5到10年前許多人開始吸收和整理這些研究內(nèi)容,想要把它們?nèi)谌敫鼮橥ㄓ玫木幊陶Z言?,F(xiàn)在的編程語言,如C#、、Ruby、Scala等等,它們都受到了函數(shù)式編程語言的影響。

              

           

            我想在這里先花幾分鐘時間簡單介紹一下我眼中的函數(shù)式編程語言。我發(fā)現(xiàn)很多人聽說過函數(shù)式編程語言,但還不十分清楚它們和普通的命令式編程語言究竟有什么區(qū)別。如今我們在使用命令式編程語言寫程序時,我們經(jīng)常會寫這樣的語句,嗨,x等于x加一,此時我們大量依賴的是狀態(tài),可變的狀態(tài),或者說變量,它們的值可以隨程序運(yùn)行而改變。

            可變狀態(tài)非常強(qiáng)大,但隨之而來的便是叫做“副作用”的問題。在使用可變狀態(tài)時,你的程序則會包含副作用,比如你會寫一個無需參數(shù)的void方法,然后它會根據(jù)你的調(diào)用次數(shù)或是在哪個線程上進(jìn)行調(diào)用對程序產(chǎn)生影響,因?yàn)関oid方法會改變程序內(nèi)部的狀態(tài),從而影響之后的運(yùn)行效果。

            而在函數(shù)式編程中則不會出現(xiàn)這個情況,因?yàn)樗械臓顟B(tài)都是不可變的。你可以聲明一個狀態(tài),但是不能改變這個狀態(tài)。而且由于你無法改變它,所以在函數(shù)式編程中不需要變量。事實(shí)上對函數(shù)式編程的討論更像是數(shù)學(xué)、公式,而不像是程序語句。如果你把x = x + 1這句話交給一個程序員看,他會說“啊,你在增加x的值”,而如果你把它交給一個數(shù)學(xué)家看,他會說“嗯,我知道這不是true”。

              

           

            然而,如果你給他看這條語言,他會說“啊,y等于x加一,就是把x + 1的計算結(jié)果交給y,你是為這個計算指定了一個名字”。這時候在思考時就是另一種方式了,這里y不是一個變量,它只是x + 1的名稱,它不會改變,永遠(yuǎn)代表了x + 1。

            所以在函數(shù)式編程語言中,當(dāng)你寫了一個函數(shù),接受一些參數(shù),那么當(dāng)你調(diào)用這個函數(shù)時,影響函數(shù)調(diào)用的只是你傳進(jìn)去的參數(shù),而你得到的也只是計算結(jié)果。在一個純函數(shù)式編程語言中,函數(shù)在計算時不會對進(jìn)行一些神奇的改變,它只會使用你給它的參數(shù),然后返回結(jié)果。在函數(shù)式編程語言中,一個void方法是沒有意義的,它唯一的作用只是讓你的CPU發(fā)熱,而不能給你任何東西,也不會有副作用。當(dāng)然現(xiàn)在你可能會說,這個CPU發(fā)多少熱也是一個副作用,好吧,不過我們現(xiàn)在先不討論這個問題。

              

           

            這里的關(guān)鍵在于,你解決問題的方法和以前大不一樣了。我這里還是用代碼來說明問題。使用函數(shù)式語言寫沒有副作用的代碼,就好比在Java或C#中使用final或是readonly的成員。

            例如這里,我們有一個Point類,構(gòu)造函數(shù)接受x和y,還有一個MoveBy方法,可以把一個點(diǎn)移動一些位置。 在傳統(tǒng)的命令式編程中,我們會改變Point實(shí)例的狀態(tài),這么做在平時可能不會有什么問題。但是,如果我把一個Point對象同時交給3個API使用,然后我修改了Point,那么如何才能告訴它們狀態(tài)改變了呢?可能我們可以使用事件,blablabla,如果我們沒有事件,那么就會出現(xiàn)那些不愉快的副作用了。

            那么使用函數(shù)式編程的形式寫代碼,你的Point類還是可以包含狀態(tài),例如x和y,不過它們是readonly的,一旦初始化以后就不能改變了。MoveBy方法不能改變Point對象,它只能創(chuàng)建一個新的Point對象并返回出來。這就是一個創(chuàng)建新Point對象的函數(shù),不是嗎?這樣就可以讓調(diào)用者來決定是使用新的還是舊的Point對象,但這里不會有產(chǎn)生副作用的情況出現(xiàn)。

            在函數(shù)式編程里自然不會只有Point對象,例如我們會有集合,如Dictionary,Map,List等等,它們都是不可變的。在函數(shù)式編程中,當(dāng)我們向一個List里添加元素時,我們會得到一個新的List,它包含了新增的元素,但之前的List依然存在。所以這些數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)方式是有根本性區(qū)別的,它們的內(nèi)部結(jié)構(gòu)會設(shè)法讓這類操作變的盡可能高效。

            在函數(shù)式編程中訪問狀態(tài)是十分安全的,因?yàn)闋顟B(tài)不會改變,我可以把一個Point或List對象交給任意多的地方去訪問,完全不用擔(dān)心副作用。函數(shù)式編程的十分容易并行,因?yàn)槲以谶\(yùn)行時不會修改狀態(tài),因此無論多少線程在運(yùn)行時都可以觀察到正確的狀態(tài)。兩個函數(shù)完全無關(guān),因此它們是并行還是順序地執(zhí)行便沒有什么區(qū)別了。我們還可以有延遲計算,可以進(jìn)行Memorization,這些都是函數(shù)式編程中十分有趣的方面。

            你可能會說,那么我們?yōu)槭裁床欢加眠@種方法來寫程序呢?嗯,最終,就像我之前說的那樣,我們不能只讓CPU發(fā)熱,我們必須要把計算結(jié)果表現(xiàn)出來。那么我們在屏幕上打印內(nèi)容時,或者把數(shù)據(jù)寫入文件或是Socket時,其實(shí)就產(chǎn)生了副作用。因此真實(shí)世界中的函數(shù)式編程,往往都是把純粹的部分進(jìn)行隔離,或是進(jìn)行更細(xì)致的控制。事實(shí)上也不會有真正純粹的函數(shù)式編程語言,它們都會帶來一定的副作用或是命令式編程的能力。但是,它們默認(rèn)是函數(shù)式的,例如在函數(shù)式編程語言中,所有東西默認(rèn)都是不可變的,你必須做些額外的事情才能使用可變狀態(tài)或是產(chǎn)生危險的副作用。此時你的編程觀念便會有所不同了。

              

           

            我們在自己的環(huán)境中開發(fā)出了這樣一個函數(shù)式編程語言,F(xiàn)#,已經(jīng)包含在VS 2010中了。F#誕生于微軟劍橋研究院,由Don Syme提出,他在F#上已經(jīng)工作了5到10年了。F#使用了另一個函數(shù)式編程語言O(shè)Caml的常見核心部分,因此它是一個強(qiáng)類型語言,并支持一些如模式匹配,類型推斷等現(xiàn)代函數(shù)式編程語言的特性。在此之上,F(xiàn)#又增加了異步工作流,度量單位等較為前沿的語言功能。

            而F#最為重要的一點(diǎn)可能是,在我看來,它是第一個和工業(yè)級的框架和工具集,如.NET和Visual Studio,有深入集成的函數(shù)式編程語言。F#允許你使用整個.NET框架,它和C#也有類似的執(zhí)行期特征,例如強(qiáng)類型,而且都會生成高效的代碼等等。我想,現(xiàn)在應(yīng)該是展示一些F#代碼的時候了。

              

           

            首先我想先從F#中我最喜歡的特性講起,這是個F#命令行……(打開命令行窗口以及一個F#源文件)……F#包含了一個交互式的命令行,這允許你直接輸入代碼并執(zhí)行。例如輸入5……x等于5……然后x……顯示出x的值是5。然后讓sqr x等于x乘以x,于是我這里定義了一個簡單的函數(shù),名為sqr。于是我們就可以計算sqr 5等于25,sqr 10等于100。

            F#的使用方式十分動態(tài),但事實(shí)上它是一個強(qiáng)類型的編程語言。我們再來看看這里。這里我定義了一個計算平方和的函數(shù)sumSquares,它會遍歷每個列表中每個元素,平方后再把它們相加。讓我先用命令式的方式編寫這個函數(shù),再使用函數(shù)式的方式,這樣你可以看出其中的區(qū)別。

            let sumSquaresI l =

            let mutable acc = 0

            for x in l do

            acc <- acc + sqr x

            acc

            這里先是命令式的代碼,我們先創(chuàng)建一個累加器acc為0,然后遍歷列表l,把平方加到acc中,然后最后我返回acc。有幾件事情值得注意,首先為了創(chuàng)建一個可變的狀態(tài),我必須顯式地使用mutable進(jìn)行聲明,在默認(rèn)情況下這是不可變的。

              

           

            還有一點(diǎn),這段代碼里我沒有提供任何的類型信息。當(dāng)我把鼠標(biāo)停留在方法上時,就會顯示sumSquaresI方法接受一個int序列作為參數(shù)并返回一個int。你可能會想int是哪里來的,嗯,它是由類型推斷而來的。編譯器從這里的0發(fā)現(xiàn)acc必須是一個int,于是它發(fā)現(xiàn)這里的加號表示兩個int的相加,于是sqr函數(shù)返回的是個int,再接下來blablabla……最終它發(fā)現(xiàn)這里到處都是int。

              

           

            如果我把這里修改為浮點(diǎn)數(shù)0.0,鼠標(biāo)再停留一下,你就會發(fā)現(xiàn)這個函數(shù)接受和返回的類型都變成float了。所以這里的類型推斷功能十分強(qiáng)大,也十分方便。

              

           

            現(xiàn)在我可以選擇這個函數(shù),讓它在命令行里執(zhí)行,然后調(diào)用sumSquaresI,提供1到100的序列,就能得到結(jié)果了。

            let rec sumSquaresF l =

            match l with

            | [] -> 0

            | h :: t -> sqr h + sumSquaresF t

            那么現(xiàn)在我們來換一種函數(shù)式的風(fēng)格。這里是另一種寫法,可以說是純函數(shù)式的實(shí)現(xiàn)方式。如果你去理解這段代碼,你會發(fā)現(xiàn)有不少數(shù)學(xué)的感覺。這里我定義了sumSqauresF函數(shù),輸入一個l列表,然后使用下面的模式去匹配l。如果它為空,則結(jié)果為0,否則把列表匹配為頭部和尾部,然后便將頭部的平方和尾部的平方和相加。

            你會發(fā)現(xiàn),在計算時我不會去改變?nèi)魏我粋€變量的值,我只是創(chuàng)建新的值。我這里會使用遞歸,就像在數(shù)學(xué)里我們經(jīng)常使用遞歸,把一個公式分解成幾個變化的形式,以此進(jìn)行遞歸的定義。在編程時我們也使用遞歸的做法,然后編譯器會設(shè)法幫我們轉(zhuǎn)化成尾遞歸或是循環(huán)等等。

            于是我們便可以執(zhí)行sumSquaresF函數(shù),也可以得到相同的結(jié)果。當(dāng)然實(shí)際上可能你并不會像之前這樣寫代碼,你可能會使用高階函數(shù):

            let sumSquares l = Seq.sum (Seq.map (fun x -> x * x) l )

            例如這里,我只是把函數(shù)x乘以x映射到列表上,然后相加。這樣也可以得到相同的結(jié)果,而且這可能是更典型的做法。我這里只是想說明,這個語言在編程時可能會給你帶來完全不同的感受,雖然它的執(zhí)行期特征和C#比較接近。

            這便是關(guān)于F#的內(nèi)容。

            (未完待續(xù))

           



          關(guān)鍵詞: 編程語言 Python

          評論


          相關(guān)推薦

          技術(shù)專區(qū)

          關(guān)閉