Javascript函數(shù)式編程語言
函數(shù)式編程語言
函數(shù)式編程語言是那些方便于使用函數(shù)式編程范式的語言。簡單來說,如果具備函數(shù)式編程所需的特征, 它就可以被稱為函數(shù)式語言。在多數(shù)情況下,編程的風(fēng)格實(shí)際上決定了一個(gè)程序是否是函數(shù)式的。
是什么讓一個(gè)語言具有函數(shù)式特征?
函數(shù)式編程無法用C語言來實(shí)現(xiàn)。函數(shù)式編程也無法用Java來實(shí)現(xiàn)(不包括那些通過大量變通手段實(shí)現(xiàn)的近似函數(shù)式編程)。 這些語言不包含支持函數(shù)式編程的結(jié)構(gòu)。他們是純面向?qū)ο蟮?、?yán)格非函數(shù)式的語言。
同時(shí),純函數(shù)語言也無法使用面向?qū)ο缶幊蹋热鏢cheme、Haskell以及Lisp。
然而有些語言兩種模式都支持。Python是個(gè)著名的例子,不過還有別的:Ruby,Julia,以及我們最感興趣的Javascript。 這些語言是如何支持這兩種差別如此之大的設(shè)計(jì)模式呢?它們包含兩種編程范式所需要的特征。 然而對(duì)于Javascript來說,函數(shù)式的特征似乎是被隱藏了。
但實(shí)際上,函數(shù)式語言所需要的比上述要多一些。到底函數(shù)式語言有什么特征呢?
特點(diǎn) | 命令式 | 函數(shù)式 |
---|---|---|
編程風(fēng)格 | 一步一步地執(zhí)行,并且要管理狀態(tài)的變化 | 描述問題和和所需的數(shù)據(jù)變化以解決問題 |
狀態(tài)變化 | 很重要 | 不存在 |
執(zhí)行順序 | 很重要 | 不太重要 |
主要的控制流 | 循環(huán)、條件、函數(shù)調(diào)用 | 函數(shù)調(diào)用和遞歸 |
主要的操作單元 | 結(jié)構(gòu)體和類對(duì)象 | 函數(shù)作為一等公民的對(duì)象和數(shù)據(jù)集 |
函數(shù)式語言的語法必須要顧及到特定的設(shè)計(jì)模式,比如類型推斷系統(tǒng)和匿名函數(shù)。大體上,這個(gè)語言必須實(shí)現(xiàn)lambda演算。 并且解釋器的求值策略必須是非嚴(yán)格、按需調(diào)用的(也叫做延遲執(zhí)行),它允許不變數(shù)據(jù)結(jié)構(gòu)和非嚴(yán)格、惰性求值。
譯注:這一段用了一些函數(shù)式編程的專業(yè)詞匯。lambda演算是一套函數(shù)推演的形式化系統(tǒng)(聽起來很暈), 它的先決條件是內(nèi)部函數(shù)和匿名函數(shù)。非嚴(yán)格求值和惰性求值差不多一個(gè)意思,就是并非嚴(yán)格地按照運(yùn)算規(guī)則把所有元素先計(jì)算一遍, 而是根據(jù)最終的需求只計(jì)算有用的那一部分,比如我們要取有一百個(gè)元素的數(shù)組的前三項(xiàng), 那惰性求值實(shí)際只會(huì)計(jì)算出一個(gè)具有三個(gè)元素是數(shù)組,而不會(huì)先去計(jì)算那個(gè)一百個(gè)元素的數(shù)組。
優(yōu)點(diǎn)
當(dāng)你最終掌握了函數(shù)式編程它將給你巨大的啟迪。這樣的經(jīng)驗(yàn)會(huì)讓你后面的程序員生涯更上一個(gè)臺(tái)階, 無論你是否真的會(huì)成為一個(gè)全職的函數(shù)式程序員。
不過我們現(xiàn)在不是在討論如何去學(xué)習(xí)冥想;我們正在探討如何去學(xué)習(xí)一個(gè)非常有用的工具,它將會(huì)讓你成為一個(gè)更好的程序員。
總的來說,什么是使用函數(shù)式編程真正實(shí)際的優(yōu)點(diǎn)呢?
更加簡潔的代碼
函數(shù)式編程更簡潔、更簡單、更小。它簡化了調(diào)試、測試和維護(hù)。
例如,我們需要這樣一個(gè)函數(shù),它能將二維數(shù)組轉(zhuǎn)化為一維數(shù)組。如果只用命令式的技術(shù),我們會(huì)寫成這樣:
function merge2dArrayIntoOne(arrays) { var count = arrays.length; var merged = new Array(count); var c = 0; for (var i = 0; i < count; ++i) { for (var j = 0, jlen = arrays[i].length; j < jlen; ++j) { merged[c++] = arrays[i][j]; } } return merged }
現(xiàn)在使用函數(shù)式技術(shù),可以寫成這樣:
merge2dArrayIntoOne2 = (arrays) -> arrays.reduce (memo, item) -> memo.concat item , []
var merge2dArrayIntoOne2 = function(arrays) { return arrays.reduce( function(p,n){ return p.concat(n); }, []); };
譯注:原著中代碼有誤,調(diào)用reduce函數(shù)時(shí)少了第二個(gè)參數(shù)空數(shù)組,這里已經(jīng)補(bǔ)上。
這兩個(gè)函數(shù)具有同樣的輸入并返回相同的輸出,但是函數(shù)式的例子更簡潔。
模塊化
函數(shù)式編程強(qiáng)制把大型問題拆分成解決同樣問題的更小的情形,這就意味著代碼會(huì)更加模塊化。 模塊化的程序具有更清晰的描述,更易調(diào)試,維護(hù)起來也更簡單。測試也會(huì)變得更加容易, 這是由于每一個(gè)模塊的代碼都可以單獨(dú)檢測正確性。
復(fù)用性
由于其模塊化的特性,函數(shù)式編程會(huì)有許多通用的輔助函數(shù)。你將會(huì)發(fā)現(xiàn)這里面的許多函數(shù)可以在大量不同的應(yīng)用里重用。
在后面的章節(jié)里,許多最通用的函數(shù)將會(huì)被覆蓋到。然而,作為一個(gè)函數(shù)式程序員,你將會(huì)不可避免地編寫自己的函數(shù)庫, 這些函數(shù)會(huì)被一次又一次地使用。例如一個(gè)用于在行間查找配置文件的函數(shù),如果設(shè)計(jì)好了也可以用于查找Hash表。
減少耦合
耦合是程序里模塊間的大量依賴。由于函數(shù)式編程遵循編寫一等公民的、高階的純函數(shù), 這使得它們對(duì)全局變量沒有副作用而彼此完全獨(dú)立,耦合極大程度上的減小了。 當(dāng)然,函數(shù)會(huì)不可避免地相互依賴,但是改變一個(gè)函數(shù)不會(huì)影響其他的,只要輸入和輸出的一對(duì)一映射保持正確。
數(shù)學(xué)正確性
最后一點(diǎn)更理論一些。由于根植于lambda演算,函數(shù)式編程可以在數(shù)學(xué)上證明正確性。 這對(duì)于一些研究者來說是一個(gè)巨大的優(yōu)點(diǎn),他們需要用程序來證明增長率、時(shí)間復(fù)雜度以及數(shù)學(xué)正確性。
我們來看看斐波那契數(shù)列。盡管它很少用于概念性證明以外的問題,但是用它來解釋這個(gè)概念非常好。 對(duì)一個(gè)斐波那契數(shù)列求值標(biāo)準(zhǔn)的辦法是建立一個(gè)遞歸函數(shù),像這樣:
fibonnaci(n) = fibonnaci(n-2) + fibonnaci(n–1)
還需要加上一個(gè)一般情形:
return 1 when n < 2
這使得遞歸可以終止,并且讓遞歸調(diào)用棧里的每一步從這里開始累加。
下面列出詳細(xì)步驟
var fibonacci = function(n) { if (n < 2) { return 1; }else { return fibonacci(n - 2) + fibonacci(n - 1); } } console.log( fibonacci(8) ); // Output: 34
然而,在一個(gè)懶執(zhí)行函數(shù)庫的輔助下,可以生成一個(gè)無窮大的序列,它是通過數(shù)學(xué)方程來定義整個(gè)序列的成員的。 只有那些我們最終需要的成員最后才會(huì)被計(jì)算出來。
var fibonacci2 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci2.length()); // Output: undefined console.log(fibonacci2.take(12).toArray()); // Output: [1, 1, 2, 3, 5,8, 13, 21, 34, 55, 89, 144] var fibonacci3 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci3.take(9).reverse().first(1).toArray()); //Output: [34]
第二個(gè)例子明顯更有數(shù)學(xué)的味道。它依賴Lazy.js函數(shù)庫。還有一些其它這樣的庫,比如Sloth.js、wu.js, 這些將在第三章里面講到。
我插幾句:后面這個(gè)懶執(zhí)行的例子放這似乎僅僅是來秀一下函數(shù)式編程在數(shù)學(xué)正確性上的表現(xiàn)。 更讓人奇怪的是作者還要把具有相同內(nèi)部函數(shù)的懶加載寫兩遍,完全沒意義啊…… 我覺得各位看官知道這是個(gè)懶執(zhí)就行了,不必深究。
非函數(shù)式世界中的函數(shù)式編程
函數(shù)式和非函數(shù)式編程能混合在一起嗎?盡管這是第七章的主題,但是在我們進(jìn)一步學(xué)習(xí)之前, 還是要弄明白一些東西。
這本書并沒要想要教你如何嚴(yán)格地用純函數(shù)編程來實(shí)現(xiàn)整個(gè)應(yīng)用。這樣的應(yīng)用在學(xué)術(shù)界之外不太適合。 相反,這本書是要教你如何在必要的命令式代碼之上使用純函數(shù)的設(shè)計(jì)策略。
例如,你需要在一段文本中找出頭四個(gè)只含有字母的單詞,稚嫩一些的寫法會(huì)是這樣:
var words = [], count = 0; text = myString.split(' '); for (i=0; count < 4, i < text.length; i++) { if (!text[i].match(/[0-9]/)) { words = words.concat(text[i]); count++; } } console.log(words);
函數(shù)式編程會(huì)寫成這樣:
var words = []; var words = myString.split(' ').filter(function(x){ return (! x.match(/[1-9]+/)); }).slice(0,4); console.log(words);
如果有一個(gè)函數(shù)式編程的工具庫,代碼可以進(jìn)一步被簡化:
var words = toSequence(myString).match(/[a-zA-Z]+/).first(4);
判斷一個(gè)函數(shù)是否能被寫成更加函數(shù)式的方式是尋找循環(huán)和臨時(shí)變量,比如前面例子里面的“words”和”count”變量。 我們通??梢杂酶唠A函數(shù)來替換循環(huán)和臨時(shí)變量,本章后面的部分將對(duì)其繼續(xù)探索。
Javascript是函數(shù)式編程語言嗎?
現(xiàn)在還有最后一個(gè)問題我們需要問問自己,Javascript是函數(shù)式語言還是非函數(shù)式語言?
Javascript可以說是世界上最流行卻最沒有被理解的函數(shù)式編程語言。Javascript是一個(gè)披著C外衣的函數(shù)式編程語言。 它的語法無疑和C比較像,這意味著它使用C語言的塊式語法和中綴語序。并且它是現(xiàn)存語言中名字起得最差勁的。 你不用去想象就可以看出來有多少人會(huì)因Javascript和Java的關(guān)系而迷惑,就好像它的名字暗示了它會(huì)是什么樣的東西! 但實(shí)際上它和Java的共同點(diǎn)非常少。不過還真有一些要把Javascript強(qiáng)制弄成面向?qū)ο笳Z言的主意, 比如Dojo、ease.js這些庫曾做了大量工作試圖抽象Javascript以使其適合面向?qū)ο缶幊獭?Javascript來自于90年代那個(gè)滿世界都嚷嚷著面向?qū)ο蟮臅r(shí)代,我們被告知Javascript是一個(gè)面向?qū)ο笳Z言是因?yàn)槲覀兿M沁@樣, 但實(shí)際上它不是。
它的真實(shí)身份可以追溯到它的原型:Scheme和Lisp,兩個(gè)經(jīng)典的函數(shù)式編程語言。Javascript一直都是一個(gè)函數(shù)式編程語言。 它的函數(shù)是頭等公民,并且可以嵌套,它具有閉包和復(fù)合函數(shù),它允許珂理化和monad。所有這些都是函數(shù)式編程的關(guān)鍵。 這里另外還有一些Javascript是函數(shù)式語言的原因:
• Javascript的詞法包括了傳遞函數(shù)為參數(shù)的能力,具有類型推斷系統(tǒng),支持匿名函數(shù)、高階函數(shù)、閉包等等。 這些特點(diǎn)對(duì)構(gòu)成函數(shù)式編程的結(jié)構(gòu)和行為至關(guān)重要。
• Javascript不是一個(gè)純面向?qū)ο笳Z言,它的多數(shù)面向?qū)ο笤O(shè)計(jì)模式都是通過拷貝Prototype對(duì)象來完成的, 這是一個(gè)弱面向?qū)ο缶幊痰哪P汀W洲電腦制造商協(xié)會(huì)腳本(ECMAScript)——Javascript的正式形式和標(biāo)準(zhǔn)實(shí)現(xiàn) ——在4.2.1版本的規(guī)范里有如下陳述:
“Javascript不具有像C++、Smalltalk、Java那樣的真正的類,但是支持創(chuàng)建對(duì)象的構(gòu)造器。 一般來說,在基于類的面向?qū)ο笳Z言里,狀態(tài)由實(shí)例承載,方法由類承載,繼承只是針對(duì)結(jié)構(gòu)和行為。 在EMACScript里,狀態(tài)和方法由對(duì)象來承載,結(jié)構(gòu)、行為和狀態(tài)都會(huì)被繼承?!?/p>
• Javascript是一個(gè)解釋型語言。Javascript的解釋器(有時(shí)被稱為“引擎”)非常類似于Scheme的解釋器。 它們都是動(dòng)態(tài)的,都有易于組合和傳輸?shù)撵`活的數(shù)據(jù)類型,都把代碼求值為表達(dá)式塊,處理函數(shù)的方式也類似。
也就是說,Javascript的確不是一個(gè)純函數(shù)式語言。它缺乏惰性求值和內(nèi)建的不可變數(shù)據(jù)。 這是由于大多數(shù)解釋器是按名調(diào)用,而不是按需調(diào)用。Javascript由于其尾調(diào)用的處理方式也不太善于處理遞歸。 不過所有的這些問題都可以通過一些小的注意事項(xiàng)來緩和。需要無窮序列和惰性求值的非嚴(yán)格求值可以通過一個(gè)叫Lazy.js的庫來實(shí)現(xiàn)。 不可變量只需要簡單的通過編程技巧就可以實(shí)現(xiàn),不過它不是通過依賴語言層面來限制而是需要程序員自律。 尾遞歸消除可以通過一個(gè)叫Trampolining的方法實(shí)現(xiàn)。這些問題將在第六章講解。
關(guān)于Javascript是函數(shù)式語言還是面向?qū)ο笳Z言還是兩者皆是還是兩者皆非的爭論一直都很多,而且這些爭論還要繼續(xù)下去。
最后,函數(shù)式編程是通過巧妙的變化、組合、使用函數(shù)而實(shí)現(xiàn)編寫簡潔代碼的方式。而且Javascript為實(shí)現(xiàn)這些提供了很好的途徑。 如果你真要挖掘出Javascript全部的潛能,你必須學(xué)會(huì)如何將它作為一個(gè)函數(shù)式語言來使用。
相關(guān)文章
簡介EasyUI datagrid editor combogrid搜索框的實(shí)現(xiàn)
這篇文章主要介紹了EasyUI datagrid editor combogrid搜索框的實(shí)現(xiàn),涉及到EasyUI中combogrid的使用方法的相關(guān)知識(shí),非常具有參考價(jià)值,需要的朋友可以參考下2016-04-04用VsCode編輯TypeScript的實(shí)現(xiàn)方法
這篇文章主要介紹了用VsCode編輯TypeScript的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05鼠標(biāo)移動(dòng)到圖片名上,顯示圖片的簡單實(shí)例
鼠標(biāo)移動(dòng)到名(wait.gif)上,顯示圖片,鼠標(biāo)移開則不顯示圖片2013-07-07JS forEach跳出循環(huán)2種實(shí)現(xiàn)方法
這篇文章主要介紹了JS forEach跳出循環(huán)2種實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06JavaScript中關(guān)鍵字?var、let、const的區(qū)別詳解
在JavaScript中,var、let和const是用于聲明變量的關(guān)鍵字,它們之間存在一些區(qū)別,這篇文章就給大家詳細(xì)介紹一下它們之間的區(qū)別,文章通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08微信小程序授權(quán)登錄的最新實(shí)現(xiàn)方案詳解(2023年)
最近由于公司需要研究了一下微信小程序的開發(fā),特此記錄一下小程序登錄授權(quán)的流程,便于自己理解,也希望對(duì)他人有多幫助,下面這篇文章主要給大家介紹了關(guān)于微信小程序授權(quán)登錄的最新實(shí)現(xiàn)方案的相關(guān)資料,需要的朋友可以參考下2023-02-02