亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript與函數(shù)式編程解釋

 更新時(shí)間:2007年04月27日 00:00:00   作者:  
作者:月影
牢記:函數(shù)式編程不是用函數(shù)來(lái)編程?。?!
23.4函數(shù)式編程 
23.4.1 什么是函數(shù)式編程

        什么是函數(shù)式編程?如果你這么直白地詢問(wèn),會(huì)發(fā)現(xiàn)它竟是一個(gè)不太容易解釋的概念。許多在程序設(shè)計(jì)領(lǐng)域有著多年經(jīng)驗(yàn)的老手,也無(wú)法很明白地說(shuō)清楚函數(shù)式編程到底在研究些什么。函數(shù)式編程對(duì)于熟悉過(guò)程式程序設(shè)計(jì)的程序員來(lái)說(shuō)的確是一個(gè)陌生的領(lǐng)域,閉包(closure),延續(xù)(continuation),和柯里化(currying)這些概念看起來(lái)是這么的陌生,同我們熟悉的if、else、while沒(méi)有任何的相似之處。盡管函數(shù)式編程有著過(guò)程式無(wú)法比擬的優(yōu)美的數(shù)學(xué)原型,但它又是那么的高深莫測(cè),似乎只有拿著博士學(xué)位的人才玩得轉(zhuǎn)它。

        提示:這一節(jié)有點(diǎn)難,但它并不是掌握J(rèn)avaScript所必需的技能,如果你不想用JavaScript來(lái)完成那些用Lisp來(lái)完成活兒,或者不想學(xué)函數(shù)式編程這種深?yuàn)W的技巧,你完全可以跳過(guò)它們,進(jìn)入下一章的旅程。

        那么回到這個(gè)問(wèn)題,什么是函數(shù)式編程?答案很長(zhǎng)……

函數(shù)式編程第一定律:函數(shù)是第一型。

        這句話本身該如何理解?什么才是真正的第一型?我們看下面的數(shù)學(xué)概念:

        二元方程式 F(x, y) = 0,x, y 是變量, 把它寫(xiě)成 y = f(x), x是參數(shù),y是返回值,f是由x到y(tǒng)的映射關(guān)系,被稱為函數(shù)。如果又有,G(x, y, z) = 0,或者記為 z = g(x, y),g是x、y到z的映射關(guān)系,也是函數(shù)。如果g的參數(shù)x, y又滿足前面的關(guān)系y = f(x), 那么得到z = g(x, y) = g(x, f(x)),這里有兩重含義,一是f(x)是x上的函數(shù),又是函數(shù)g的參數(shù),二是g是一個(gè)比f(wàn)更高階的函數(shù)。
        這樣我們就用z = g(x, f(x)) 來(lái)表示方程F(x, y) = 0和G(x, y, z) = 0的關(guān)聯(lián)解,它是一個(gè)迭代的函數(shù)。我們也可以用另一種形式來(lái)表示g,記z = g(x, y, f),這樣我們將函數(shù)g一般化為一個(gè)高階函數(shù)。同前面相比,后面這種表示方式的好處是,它是一種更加泛化的模型,例如T(x,y) = 0和G(x,y,z) = 0的關(guān)聯(lián)解,我們也可以用同樣的形式來(lái)表示(只要令f=t)。在這種支持把問(wèn)題的解轉(zhuǎn)換成高階函數(shù)迭代的語(yǔ)言體系中,函數(shù)就被稱為“第一型”。
        JavaScript中的函數(shù)顯然是“第一型”。下面就是一個(gè)典型的例子:

        Array.prototype.each = function(closure)
                {
                return this.length ? [closure(this[0])].concat(this.slice(1).each(closure)) : [];
                }

這真是個(gè)神奇的魔法代碼,它充分發(fā)揮了函數(shù)式的魅力,在整個(gè)代碼中只有函數(shù)(function)和符號(hào)(Symbol)。它形式簡(jiǎn)潔并且威力無(wú)窮。
[1,2,3,4].each(function(x){return x * 2})得到[2,4,6,8],而[1,2,3,4].each(function(x){return x-1})得到[0,1,2,3]。

函數(shù)式和面向?qū)ο蟮谋举|(zhì)都是“道法自然”。如果說(shuō),面向?qū)ο笫且环N真實(shí)世界的模擬的話,那么函數(shù)式就是數(shù)學(xué)世界的模擬,從某種意義上說(shuō),它的抽象程度比面向?qū)ο蟾?,因?yàn)閿?shù)學(xué)系統(tǒng)本來(lái)就具有自然界所無(wú)法比擬的抽象性。

函數(shù)式編程第二定律:閉包是函數(shù)式編程的摯友。

閉包,在前面的章節(jié)中我們已經(jīng)解釋過(guò)了,它對(duì)于函數(shù)式編程非常重要。它最大的特點(diǎn)是不需要通過(guò)傳遞變量(符號(hào))的方式就可以從內(nèi)層直接訪問(wèn)外層的環(huán)境,這為多重嵌套下的函數(shù)式程序帶來(lái)了極大的便利性,下面是一個(gè)例子:

(function outerFun(x)
{
        return function innerFun(y)
        {
                return x * y;
        }
})(2)(3);

函數(shù)式編程第三定律:函數(shù)可以被科里化(Currying)。

什么是Currying? 它是一個(gè)有趣的概念。還是從數(shù)學(xué)開(kāi)始:我們說(shuō),考慮一個(gè)三維空間方程 F(x, y, z) = 0,如果我們限定z = 0,于是得到 F(x, y, 0) = 0 記為 F'(x, y)。這里F'顯然是一個(gè)新的方程式,它代表三維空間曲線F(x, y, z)在z = 0平面上的兩維投影。記y = f(x, z), 令z = 0, 得到 y = f(x, 0),記為 y = f'(x), 我們說(shuō)函數(shù)f'是f的一個(gè)Currying解。
下面給出了JavaScript的Currying的例子:
function add(x, y)
{
        if(x!=null && y!=null) return x + y;
                else if(x!=null && y==null) return function(y)
                {
                return x + y;
                }
                else if(x==null && y!=null) return function(x)
                {
                       return x + y;
                 }
}
var a = add(3, 4);
var b = add(2);
var c = b(10);

上面的例子中,b=add(2)得到的是一個(gè)add()的Currying函數(shù),它是當(dāng)x = 2時(shí),關(guān)于參數(shù)y的函數(shù),注意到上面也用到了閉包的特性。

有趣的是,我們可以給任意函數(shù)一般化Currying,例如:

function Foo(x, y, z, w)
{
        var args = arguments;

        if(Foo.length < args.length)
                return function()
                {
                        return 
args.callee.apply(Array.apply([], args).concat(Array.apply([], arguments)));
                }
        else
                return x + y – z * w;
}

函數(shù)式編程第四定律:延遲求值和延續(xù)。
        //TODO:這里再考慮下


23.4.2 函數(shù)式編程的優(yōu)點(diǎn)

單元測(cè)試

嚴(yán)格函數(shù)式編程的每一個(gè)符號(hào)都是對(duì)直接量或者表達(dá)式結(jié)果的引用,沒(méi)有函數(shù)產(chǎn)生副作用。因?yàn)閺奈丛谀硞€(gè)地方修改過(guò)值,也沒(méi)有函數(shù)修改過(guò)在其作用域之外的量并被其他函數(shù)使用(如類成員或全局變量)。這意味著函數(shù)求值的結(jié)果只是其返回值,而惟一影響其返回值的就是函數(shù)的參數(shù)。
這是單元測(cè)試者的夢(mèng)中仙境(wet dream)。對(duì)被測(cè)試程序中的每個(gè)函數(shù),你只需在意其參數(shù),而不必考慮函數(shù)調(diào)用順序,不用謹(jǐn)慎地設(shè)置外部狀態(tài)。所有要做的就是傳遞代表了邊際情況的參數(shù)。如果程序中的每個(gè)函數(shù)都通過(guò)了單元測(cè)試,你就對(duì)這個(gè)軟件的質(zhì)量有了相當(dāng)?shù)淖孕?。而命令式編程就不能這樣樂(lè)觀了,在 Java 或 C++ 中只檢查函數(shù)的返回值還不夠——我們還必須驗(yàn)證這個(gè)函數(shù)可能修改了的外部狀態(tài)。

調(diào)試

如果一個(gè)函數(shù)式程序不如你期望地運(yùn)行,調(diào)試也是輕而易舉。因?yàn)楹瘮?shù)式程序的 bug 不依賴于執(zhí)行前與其無(wú)關(guān)的代碼路徑,你遇到的問(wèn)題就總是可以再現(xiàn)。在命令式程序中,bug 時(shí)隱時(shí)現(xiàn),因?yàn)樵谀抢锖瘮?shù)的功能依賴與其他函數(shù)的副作用,你可能會(huì)在和 bug 的產(chǎn)生無(wú)關(guān)的方向探尋很久,毫無(wú)收獲。函數(shù)式程序就不是這樣——如果一個(gè)函數(shù)的結(jié)果是錯(cuò)誤的,那么無(wú)論之前你還執(zhí)行過(guò)什么,這個(gè)函數(shù)總是返回相同的錯(cuò)誤結(jié)果。
一旦你將那個(gè)問(wèn)題再現(xiàn)出來(lái),尋其根源將毫不費(fèi)力,甚至?xí)屇汩_(kāi)心。中斷那個(gè)程序的執(zhí)行然后檢查堆棧,和命令式編程一樣,棧里每一次函數(shù)調(diào)用的參數(shù)都呈現(xiàn)在你眼前。但是在命令式程序中只有這些參數(shù)還不夠,函數(shù)還依賴于成員變量,全局變量和類的狀態(tài)(這反過(guò)來(lái)也依賴著這許多情況)。函數(shù)式程序里函數(shù)只依賴于它的參數(shù),而那些信息就在你注視的目光下!還有,在命令式程序里,只檢查一個(gè)函數(shù)的返回值不能夠讓你確信這個(gè)函數(shù)已經(jīng)正常工作了,你還要去查看那個(gè)函數(shù)作用域外數(shù)十個(gè)對(duì)象的狀態(tài)來(lái)確認(rèn)。對(duì)函數(shù)式程序,你要做的所有事就是查看其返回值!
沿著堆棧檢查函數(shù)的參數(shù)和返回值,只要發(fā)現(xiàn)一個(gè)不盡合理的結(jié)果就進(jìn)入那個(gè)函數(shù)然后一步步跟蹤下去,重復(fù)這一個(gè)過(guò)程,直到它讓你發(fā)現(xiàn)了 bug 的生成點(diǎn)。

并行
函數(shù)式程序無(wú)需任何修改即可并行執(zhí)行。不用擔(dān)心死鎖和臨界區(qū),因?yàn)槟銖奈从面i!函數(shù)式程序里沒(méi)有任何數(shù)據(jù)被同一線程修改兩次,更不用說(shuō)兩個(gè)不同的線程了。這意味著可以不假思索地簡(jiǎn)單增加線程而不會(huì)引發(fā)折磨著并行應(yīng)用程序的傳統(tǒng)問(wèn)題。
事實(shí)既然如此,為什么并不是所有人都在需要高度并行作業(yè)的應(yīng)用中采用函數(shù)式程序?嗯,他們正在這樣做。愛(ài)立信公司設(shè)計(jì)了一種叫作 Erlang 的函數(shù)式語(yǔ)言并將它使用在需要極高抗錯(cuò)性和可擴(kuò)展性的電信交換機(jī)上。還有很多人也發(fā)現(xiàn)了 Erlang 的優(yōu)勢(shì)并開(kāi)始使用它。我們談?wù)摰氖请娦磐ㄐ趴刂葡到y(tǒng),這與設(shè)計(jì)華爾街的典型系統(tǒng)相比對(duì)可靠性和可升級(jí)性要求高了得多。實(shí)際上,Erlang 系統(tǒng)并不可靠和易擴(kuò)展,JavaScript 才是。Erlang 系統(tǒng)只是堅(jiān)如磐石。
關(guān)于并行的故事還沒(méi)有就此停止,即使你的程序本身就是單線程的,那么函數(shù)式程序的編譯器仍然可以優(yōu)化它使其運(yùn)行于多個(gè)CPU上。請(qǐng)看下面這段代碼:

String s1 = somewhatLongOperation1();
String s2 = somewhatLongOperation2();
String s3 = concatenate(s1, s2);

在函數(shù)編程語(yǔ)言中,編譯器會(huì)分析代碼,辨認(rèn)出潛在耗時(shí)的創(chuàng)建字符串s1和s2的函數(shù),然后并行地運(yùn)行它們。這在命令式語(yǔ)言中是不可能的,因?yàn)樵谀抢铮總€(gè)函數(shù)都有可能修改了函數(shù)作用域以外的狀態(tài)并且其后續(xù)的函數(shù)又會(huì)依賴這些修改。在函數(shù)式語(yǔ)言里,自動(dòng)分析函數(shù)并找出適合并行執(zhí)行的候選函數(shù)簡(jiǎn)單的像自動(dòng)進(jìn)行的函數(shù)內(nèi)聯(lián)化!在這個(gè)意義上,函數(shù)式風(fēng)格的程序是“不會(huì)過(guò)時(shí)的技術(shù)(future proof)”(即使不喜歡用行業(yè)術(shù)語(yǔ),但這回要破例一次)。硬件廠商已經(jīng)無(wú)法讓CPU運(yùn)行得更快了,于是他們?cè)黾恿颂幚砥骱诵牡乃俣炔⒁虿⑿卸@得了四倍的速度提升。當(dāng)然他們也順便忘記提及我們的多花的錢(qián)只是用在了解決平行問(wèn)題的軟件上了。一小部分的命令式軟件和 100% 的函數(shù)式軟件都可以直接并行運(yùn)行于這些機(jī)器上。

代碼熱部署

過(guò)去要在 Windows上安裝更新,重啟計(jì)算機(jī)是難免的,而且還不只一次,即使是安裝了一個(gè)新版的媒體播放器。Windows XP 大大改進(jìn)了這一狀態(tài),但仍不理想(我今天工作時(shí)運(yùn)行了Windows Update,現(xiàn)在一個(gè)煩人的圖標(biāo)總是顯示在托盤(pán)里除非我重啟一次機(jī)器)。Unix系統(tǒng)一直以來(lái)以更好的模式運(yùn)行,安裝更新時(shí)只需停止系統(tǒng)相關(guān)的組件,而不是整個(gè)操作系統(tǒng)。即使如此,對(duì)一個(gè)大規(guī)模的服務(wù)器應(yīng)用這還是不能令人滿意的。電信系統(tǒng)必須100%的時(shí)間運(yùn)行,因?yàn)槿绻谙到y(tǒng)更新時(shí)緊急撥號(hào)失效,就可能造成生命的損失。華爾街的公司也沒(méi)有理由必須在周末停止服務(wù)以安裝更新。
理想的情況是完全不停止系統(tǒng)任何組件來(lái)更新相關(guān)的代碼。在命令式的世界里這是不可能的??紤]運(yùn)行時(shí)上載一個(gè)Java類并重載一個(gè)新的定義,那么所有這個(gè)類的實(shí)例都將不可用,因?yàn)樗鼈儽槐4娴臓顟B(tài)丟失了。我們可以著手寫(xiě)些繁瑣的版本控制代碼來(lái)解決這個(gè)問(wèn)題,然后將這個(gè)類的所有實(shí)例序列化,再銷(xiāo)毀這些實(shí)例,繼而用這個(gè)類新的定義來(lái)重新創(chuàng)建這些實(shí)例,然后載入先前被序列化的數(shù)據(jù)并希望載入代碼可以恰到地將這些數(shù)據(jù)移植到新的實(shí)例。在此之上,每次更新都要重新手動(dòng)編寫(xiě)這些用來(lái)移植的代碼,而且要相當(dāng)謹(jǐn)慎地防止破壞對(duì)象間的相互關(guān)系。理論簡(jiǎn)單,但實(shí)踐可不容易。
對(duì)函數(shù)式的程序,所有的狀態(tài)即傳遞給函數(shù)的參數(shù)都被保存在了堆棧上,這使的熱部署輕而易舉!實(shí)際上,所有我們需要做的就是對(duì)工作中的代碼和新版本的代碼做一個(gè)差異比較,然后部署新代碼。其他的工作將由一個(gè)語(yǔ)言工具自動(dòng)完成!如果你認(rèn)為這是個(gè)科幻故事,請(qǐng)?jiān)偎伎家幌?。多年?lái) Erlang工程師一直更新著他們的運(yùn)轉(zhuǎn)著的系統(tǒng),而無(wú)需中斷它。

機(jī)器輔助的推理和優(yōu)化

函數(shù)式語(yǔ)言的一個(gè)有趣的屬性就是他們可以用數(shù)學(xué)方式推理。因?yàn)橐环N函數(shù)式語(yǔ)言只是一個(gè)形式系統(tǒng)的實(shí)現(xiàn),所有在紙上完成的運(yùn)算都可以應(yīng)用于用這種語(yǔ)言書(shū)寫(xiě)的程序。編譯器可以用數(shù)學(xué)理論將轉(zhuǎn)換一段代碼轉(zhuǎn)換為等價(jià)的但卻更高效的代碼[7]。多年來(lái)關(guān)系數(shù)據(jù)庫(kù)一直在進(jìn)行著這類優(yōu)化。沒(méi)有理由不能把這一技術(shù)應(yīng)用到常規(guī)軟件上。
另外,還能使用這些技術(shù)來(lái)證明部分程序的正確,甚至可能創(chuàng)建工具來(lái)分析代碼并為單元測(cè)試自動(dòng)生成邊界用例!對(duì)穩(wěn)固的系統(tǒng)這種功能沒(méi)有價(jià)值,但如果你要設(shè)計(jì)心房脈沖產(chǎn)生器 (pace maker)或空中交通控制系統(tǒng),這種工具就不可或缺。如果你編寫(xiě)的應(yīng)用程序不是產(chǎn)業(yè)的核心任務(wù),這類工具也是你強(qiáng)于競(jìng)爭(zhēng)對(duì)手的殺手锏。

23.4.3 函數(shù)式編程的缺點(diǎn)

閉包的副作用

        非嚴(yán)格函數(shù)式編程中,閉包可以改寫(xiě)外部環(huán)境(在上一章中我們已經(jīng)見(jiàn)過(guò)了),這帶來(lái)了副作用,當(dāng)這種副作用頻繁出現(xiàn)并經(jīng)常改變程序運(yùn)行環(huán)境時(shí),錯(cuò)誤就變得難以跟蹤。
        //TODO:

遞歸的形式

        盡管遞歸通常是一種最簡(jiǎn)潔的表達(dá)形式,但它確實(shí)不如非遞歸的循環(huán)來(lái)的直觀。
        //TODO:

延遲取值的弱點(diǎn)

        //TODO:

相關(guān)文章

最新評(píng)論