摘要:把數(shù)據(jù)的流向想象成糖果工廠的一條傳送帶,每一次操作其實(shí)都是冷卻切割包裝糖果中的一步。在該章節(jié)中,我們將會(huì)用糖果工廠的類比來(lái)解釋什么是組合。糖果工廠靠這套流程運(yùn)營(yíng)的很成功,但是和所有的商業(yè)公司一樣,管理者們需要不停的尋找增長(zhǎng)點(diǎn)。
原文地址:Functional-Light-JS
原文作者:Kyle Simpson-《You-Dont-Know-JS》作者
JavaScript輕量級(jí)函數(shù)式編程 第 4 章:組合函數(shù)關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;分享,是 CSS 里最閃耀的一瞥;總結(jié),是 JavaScript 中最嚴(yán)謹(jǐn)?shù)倪壿嫛=?jīng)過(guò)捶打磨練,成就了本書的中文版。本書包含了函數(shù)式編程之精髓,希望可以幫助大家在學(xué)習(xí)函數(shù)式編程的道路上走的更順暢。比心。
譯者團(tuán)隊(duì)(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、蘿卜、vavd317、vivaxy、萌萌、zhouyao
到目前為止,我希望你能更輕松地理解在函數(shù)式編程中使用函數(shù)意味著什么。
一個(gè)函數(shù)式編程者,會(huì)將他們程序中的每一個(gè)函數(shù)當(dāng)成一小塊簡(jiǎn)單的樂(lè)高部件。他們能一眼辨別出藍(lán)色的 2x2 方塊,并準(zhǔn)確地知道它是如何工作的、能用它做些什么。當(dāng)構(gòu)建一個(gè)更大、更復(fù)雜的樂(lè)高模型時(shí),當(dāng)每一次需要下一塊部件的時(shí)候,他們能夠準(zhǔn)確地從備用部件中找到這些部件并拿過(guò)來(lái)使用。
但有些時(shí)候,你把藍(lán)色 2x2 的方塊和灰色 4x1 的方塊以某種形式組裝到一起,然后意識(shí)到:“這是個(gè)有用的部件,我可能會(huì)常用到它”。
那么你現(xiàn)在想到了一種新的“部件”,它是兩種其他部件的組合,在需要的時(shí)候能觸手可及。這時(shí)候,將這個(gè)藍(lán)黑色 L 形狀的方塊組合體放到需要使用的地方,比每次分開(kāi)考慮兩種獨(dú)立方塊的組合要有效的多。
函數(shù)有多種多樣的形狀和大小。我們能夠定義某種組合方式,來(lái)讓它們成為一種新的組合函數(shù),程序中不同的部分都可以使用這個(gè)函數(shù)。這種將函數(shù)一起使用的過(guò)程叫做組合。
輸出到輸入我們已經(jīng)見(jiàn)過(guò)幾種組合的例子。比如,在第 3 章中,我們對(duì) unary(..) 的討論包含了如下表達(dá)式:unary(adder(3))。仔細(xì)想想這里發(fā)生了什么。
為了將兩個(gè)函數(shù)整合起來(lái),將第一個(gè)函數(shù)調(diào)用產(chǎn)生的輸出當(dāng)做第二個(gè)函數(shù)調(diào)用的輸入。在 unary(adder(3)) 中,adder(3) 的調(diào)用返回了一個(gè)值(值是一個(gè)函數(shù));該值被直接作為一個(gè)參數(shù)傳入到 unary(..) 中,同樣的,這個(gè)調(diào)用返回了一個(gè)值(值為另一個(gè)函數(shù))。
讓我們回放一下過(guò)程并且將數(shù)據(jù)流動(dòng)的概念視覺(jué)化,是這個(gè)樣子:
functionValue <-- unary <-- adder <-- 3
3 是 adder(..) 的輸入。而 adder(..) 的輸出是 unary(..) 的輸入。unary(..) 的輸出是 functionValue。 這就是 unary(..) 和 adder(..) 的組合。
把數(shù)據(jù)的流向想象成糖果工廠的一條傳送帶,每一次操作其實(shí)都是冷卻、切割、包裝糖果中的一步。在該章節(jié)中,我們將會(huì)用糖果工廠的類比來(lái)解釋什么是組合。
讓我們一步一步的來(lái)了解組合。首先假設(shè)你程序中可能存在這么兩個(gè)實(shí)用函數(shù)。
function words(str) { return String( str ) .toLowerCase() .split( /s|/ ) .filter( function alpha(v){ return /^[w]+$/.test( v ); } ); } function unique(list) { var uniqList = []; for (let i = 0; i < list.length; i++) { // value not yet in the new list? if (uniqList.indexOf( list[i] ) === -1 ) { uniqList.push( list[i] ); } } return uniqList; }
使用這兩個(gè)實(shí)用函數(shù)來(lái)分析文本字符串:
var text = "To compose two functions together, pass the output of the first function call as the input of the second function call."; var wordsFound = words( text ); var wordsUsed = unique( wordsFound ); wordsUsed; // ["to","compose","two","functions","together","pass", // "the","output","of","first","function","call","as", // "input","second"]
我們把 words(..) 輸出的數(shù)組命名為 wordsFound。unique(..) 的輸入也是一個(gè)數(shù)組,因此我們可以將 wordsFound 傳入給它。
讓我們重新回到糖果工廠的流水線:第一臺(tái)機(jī)器接收的“輸入”是融化的巧克力,它的“輸出”是一堆成型且冷卻的巧克力。流水線上的下一個(gè)機(jī)器將這堆巧克力作為它的“輸入”,它的“輸出”是一片片切好的巧克力糖果。下一步就是,流水線上的另一臺(tái)機(jī)器將這些傳送帶上的小片巧克力糖果處理,并輸出成包裝好的糖果,準(zhǔn)備打包和運(yùn)輸。
糖果工廠靠這套流程運(yùn)營(yíng)的很成功,但是和所有的商業(yè)公司一樣,管理者們需要不停的尋找增長(zhǎng)點(diǎn)。
為了跟上更多糖果的生產(chǎn)需求,他們決定拿掉傳送帶這么個(gè)玩意,直接把三臺(tái)機(jī)器疊在一起,這樣第一臺(tái)的輸出閥就直接和下一臺(tái)的輸入閥直接連一起了。這樣第一臺(tái)機(jī)器和第二臺(tái)機(jī)器之間,就再也不會(huì)有一堆巧克力在傳送帶上慢吞吞的移動(dòng)了,并且也不會(huì)有空間浪費(fèi)和隆隆的噪音聲了。
這項(xiàng)革新為工廠節(jié)省了很大的空間,所以管理者很高興,他們每天能夠造更多的糖果了!
等價(jià)于這種升級(jí)后的糖果工廠配置的代碼跳過(guò)了中間步驟(上面代碼片段中的 wordsFound 變量),僅僅是將兩個(gè)函數(shù)調(diào)用一起使用:
var wordsUsed = unique( words( text ) );
注意: 盡管我們通常以從左往右的方式閱讀函數(shù)調(diào)用 ———— 先 unique(..) 然后 words(..) ———— 這里的操作順序?qū)嶋H上是從右往左的,或者說(shuō)是自內(nèi)而外。words(..) 將會(huì)首先運(yùn)行,然后才是 unique(..)。晚點(diǎn)我們會(huì)討論符合我們自然的、從左往右閱讀執(zhí)行順序的模式,叫做 pipe(..)。
堆在一起的機(jī)器工作的還不錯(cuò),但有些笨重了,電線掛的到處都是。創(chuàng)造的機(jī)器堆越多,工廠車間就會(huì)變得越凌亂。而且,裝配和維護(hù)這些機(jī)器堆太占用時(shí)間了。
有一天早上,一個(gè)糖果工廠的工程師突然想到了一個(gè)好點(diǎn)子。她想,如果她能在外面做一個(gè)大盒子把所有的電線都藏起來(lái),效果肯定超級(jí)棒;盒子里面,三臺(tái)機(jī)器相互連接,而盒子外面,一切都變得很整潔、干凈。在這個(gè)很贊的機(jī)器的頂部,是傾倒融化巧克力的管道,在它的底部,是吐出包裝好的巧克力糖果的管道。
這樣一個(gè)單個(gè)的組合版機(jī)器,變得更易移動(dòng)和安裝到工廠需要的地方中去了。工廠的車間工人也會(huì)變得更高興,因?yàn)樗麄儾挥迷贁[弄三臺(tái)機(jī)子上的那些按鈕和表盤了;他們很快更喜歡使用這個(gè)獨(dú)立的很贊的機(jī)器。
回到代碼上:我們現(xiàn)在了解到 words(..) 和 unique(..) 執(zhí)行的特定順序 -- 思考:組合的樂(lè)高 -- 是一種我們?cè)趹?yīng)用中其它部分也能夠用到的東西。所以,現(xiàn)在讓我們定義一個(gè)組合這些玩意的函數(shù):
function uniqueWords(str) { return unique( words( str ) ); }
uniqueWords(..) 接收一個(gè)字符串并返回一個(gè)數(shù)組。它是 unique(..) 和 words(..) 的組合,并且滿足我們的數(shù)據(jù)流向要求:
wordsUsed <-- unique <-- words <-- text
你現(xiàn)在應(yīng)該能夠明白了:糖果工廠設(shè)計(jì)模式的演變革命就是函數(shù)的組合。
制造機(jī)器糖果工廠一切運(yùn)轉(zhuǎn)良好,多虧了省下的空間,他們現(xiàn)在有足夠多的地方來(lái)嘗試制作新的糖果了。鑒于之前的成功,管理者迫切的想要發(fā)明新的棒棒的組合版機(jī)器,從而制造越來(lái)越多種類的糖果。
但工廠的工程師們跟不上老板的節(jié)奏,因?yàn)槊看卧煲慌_(tái)新的棒棒的組合版機(jī)器,他們就要花費(fèi)很多的時(shí)間來(lái)造新的外殼,從而適應(yīng)那些獨(dú)立的機(jī)器。
所以工程師們聯(lián)系了一家工業(yè)機(jī)器制供應(yīng)商來(lái)幫他們。他們很驚訝的發(fā)現(xiàn)這家供應(yīng)商竟然提供 機(jī)器制造 器!聽(tīng)起來(lái)好像不可思議,他們買入了一臺(tái)這樣的機(jī)器,這臺(tái)機(jī)器能夠?qū)⒐S中小一點(diǎn)的機(jī)器 ———— 比如負(fù)責(zé)巧克力冷卻、切割的機(jī)器 ———— 自動(dòng)連線,甚至在外面還自動(dòng)包了一個(gè)干凈的大盒子。這么牛的機(jī)器簡(jiǎn)直能把這家糖果工廠送上天了!
回到代碼上,讓我們定義一個(gè)實(shí)用函數(shù)叫做 compose2(..),它能夠自動(dòng)創(chuàng)建兩個(gè)函數(shù)的組合,這和我們手動(dòng)做的是一模一樣的。
function compose2(fn2,fn1) { return function composed(origValue){ return fn2( fn1( origValue ) ); }; } // ES6 箭頭函數(shù)形式寫法 var compose2 = (fn2,fn1) => origValue => fn2( fn1( origValue ) );
你是否注意到我們定義參數(shù)的順序是 fn2,fn1,不僅如此,參數(shù)中列出的第二個(gè)函數(shù)(也被稱作 fn1)會(huì)首先運(yùn)行,然后才是參數(shù)中的第一個(gè)函數(shù)(fn2)?換句話說(shuō),這些函數(shù)是以從右往左的順序組合的。
這看起來(lái)是種奇怪的實(shí)現(xiàn),但這是有原因的。大部分傳統(tǒng)的 FP 庫(kù)為了順序而將它們的 compose(..) 定義為從右往左的工作,所以我們沿襲了這種慣例。
但是為什么這么做?我認(rèn)為最簡(jiǎn)單的解釋(但不一定符合真實(shí)的歷史)就是我們?cè)谝允謩?dòng)執(zhí)行的書寫順序來(lái)列出它們時(shí),或是與我們從左往右閱讀這個(gè)列表時(shí)看到它們的順序相符合。
unique(words(str)) 以從左往右的順序列出了 unique, words 函數(shù),所以我們讓 compose2(..) 實(shí)用函數(shù)也以這種順序接收它們。現(xiàn)在,更高效的糖果制造機(jī)定義如下:
var uniqueWords = compose2( unique, words );組合的變體
看起來(lái)貌似 <-- unique <-- words 的組合方式是這兩種函數(shù)能夠被組合起來(lái)的唯一順序。但我們實(shí)際上能夠以另外的目的創(chuàng)建一個(gè)實(shí)用函數(shù),將它們以相反的順序組合起來(lái)。
var letters = compose2( words, unique ); var chars = letters( "How are you Henry?" ); chars; // ["h","o","w","a","r","e","y","u","n"]
因?yàn)?words(..) 實(shí)用函數(shù),上面的代碼才能正常工作。為了值類型的安全,首先使用 String(..) 將它的輸入強(qiáng)轉(zhuǎn)為一個(gè)字符串。所以 unique(..) 返回的數(shù)組 -- 現(xiàn)在是 words(..) 的輸入 -- 成為了 "H,o,w, ,a,r,e,y,u,n,?" 這樣的字符串。然后 words(..) 中的行為將字符串處理成為 chars 數(shù)組。
不得不承認(rèn),這是個(gè)刻意的例子。但重點(diǎn)是,函數(shù)的組合不總是單向的。有時(shí)候我們將灰方塊放到藍(lán)方塊上,有時(shí)我們又會(huì)將藍(lán)方塊放到最上面。
假如糖果工廠嘗試將包裝好的糖果放入攪拌和冷卻巧克力的機(jī)器,那他們最好要小心點(diǎn)了。
通用組合如果我們能夠定義兩個(gè)函數(shù)的組合,我們也同樣能夠支持組合任意數(shù)量的函數(shù)。任意數(shù)目函數(shù)的組合的通用可視化數(shù)據(jù)流如下:
finalValue <-- func1 <-- func2 <-- ... <-- funcN <-- origValue
現(xiàn)在糖果工廠擁有了最好的制造機(jī):它能夠接收任意數(shù)量獨(dú)立的小機(jī)器,并吐出一個(gè)大只的、超贊的機(jī)器,能把每一步都按照順序做好。這個(gè)糖果制作流程簡(jiǎn)直棒呆了!簡(jiǎn)直是威利·旺卡(譯者注:《查理和巧克力工廠》中的人物,他擁有一座巧克力工廠)的夢(mèng)想!
我們能夠像這樣實(shí)現(xiàn)一個(gè)通用 compose(..) 實(shí)用函數(shù):
function compose(...fns) { return function composed(result){ // 拷貝一份保存函數(shù)的數(shù)組 var list = fns.slice(); while (list.length > 0) { // 將最后一個(gè)函數(shù)從列表尾部拿出 // 并執(zhí)行它 result = list.pop()( result ); } return result; }; } // ES6 箭頭函數(shù)形式寫法 var compose = (...fns) => result => { var list = fns.slice(); while (list.length > 0) { // 將最后一個(gè)函數(shù)從列表尾部拿出 // 并執(zhí)行它 result = list.pop()( result ); } return result; };
現(xiàn)在看一下組合超過(guò)兩個(gè)函數(shù)的例子。回想下我們的 uniqueWords(..) 組合例子,讓我們?cè)黾右粋€(gè) skipShortWords(..)。
function skipShortWords(list) { var filteredList = []; for (let i = 0; i < list.length; i++) { if (list[i].length > 4) { filteredList.push( list[i] ); } } return filteredList; }
讓我們?cè)俣x一個(gè) biggerWords(..) 來(lái)包含 skipShortWords(..)。我們期望等價(jià)的手工組合方式是 skipShortWords(unique(words(text))),所以讓我們采用 compose(..) 來(lái)實(shí)現(xiàn)它:
var text = "To compose two functions together, pass the output of the first function call as the input of the second function call."; var biggerWords = compose( skipShortWords, unique, words ); var wordsUsed = biggerWords( text ); wordsUsed; // ["compose","functions","together","output","first", // "function","input","second"]
現(xiàn)在,讓我們回憶一下第 3 章中出現(xiàn)的 partialRight(..) 來(lái)讓組合變的更有趣。我們能夠構(gòu)造一個(gè)由 compose(..) 自身組成的右偏函數(shù)應(yīng)用,通過(guò)提前定義好第二和第三參數(shù)(unique(..) 和 words(..));我們把它稱作 filterWords(..)(如下)。
然后,我們能夠通過(guò)多次調(diào)用 filterWords(..) 來(lái)完成組合,但是每次的第一參數(shù)卻各不相同。
// 注意: 使用 a <= 4 來(lái)檢查,而不是 skipShortWords(..) 中用到的 > 4 function skipLongWords(list) { /* .. */ } var filterWords = partialRight( compose, unique, words ); var biggerWords = filterWords( skipShortWords ); var shorterWords = filterWords( skipLongWords ); biggerWords( text ); // ["compose","functions","together","output","first", // "function","input","second"] shorterWords( text ); // ["to","two","pass","the","of","call","as"]
花些時(shí)間考慮一下基于 compose(..) 的右偏函數(shù)應(yīng)用給了我們什么。它允許我們?cè)诮M合的第一步之前做指定,然后以不同后期步驟 (biggerWords(..) and shorterWords(..)) 的組合來(lái)創(chuàng)建特定的變體。這是函數(shù)式編程中最強(qiáng)大的手段之一。
你也能通過(guò) curry(..) 創(chuàng)建的組合來(lái)替代偏函數(shù)應(yīng)用,但因?yàn)閺挠彝蟮捻樞颍绕鹬皇褂?curry( compose, ..),你可能更想使用 curry( reverseArgs(compose), ..)。
注意: 因?yàn)?curry(..)(至少我們?cè)诘?3 章中實(shí)現(xiàn)的是這樣)依賴于探測(cè)參數(shù)數(shù)目(length)或手動(dòng)指定其數(shù)目,而 compose(..) 是一個(gè)可變的函數(shù),所以你需要手動(dòng)指定數(shù)目,就像這樣 curry(.. , 3)。
不同的實(shí)現(xiàn)當(dāng)然,你可能永遠(yuǎn)不會(huì)在生產(chǎn)中使用自己寫的 compose(..),而更傾向于使用某個(gè)庫(kù)所提供的方案。但我發(fā)現(xiàn)了解底層工作的原理實(shí)際上對(duì)強(qiáng)化理解函數(shù)式編程中通用概念非常有用。
所以讓我們看看對(duì)于 compose(..) 的不同實(shí)現(xiàn)方案。我們能看到每一種實(shí)現(xiàn)的優(yōu)缺點(diǎn),特別是性能方面。
我們將稍后在文中查看 reduce(..) 實(shí)用函數(shù)的細(xì)節(jié),但現(xiàn)在,只需了解它將一個(gè)列表(數(shù)組)簡(jiǎn)化為一個(gè)單一的有限值。看起來(lái)像是一個(gè)很棒的循環(huán)體。
舉個(gè)例子,如果在數(shù)字列表 [1,2,3,4,5,6] 上做加法約減,你將要循環(huán)它們,并且隨著循環(huán)將它們加在一起。這一過(guò)程將首先將 1 加 2,然后將結(jié)果加 3,然后加 4,等等。最后得到總和:21。
原始版本的 compose(..) 使用一個(gè)循環(huán)并且饑渴的(也就是,立刻)執(zhí)行計(jì)算,將一個(gè)調(diào)用的結(jié)果傳遞到下一個(gè)調(diào)用。我們可以通過(guò) reduce(..) (代替循環(huán))做到同樣的事。
function compose(...fns) { return function composed(result){ return fns.reverse().reduce( function reducer(result,fn){ return fn( result ); }, result ); }; } // ES6 箭頭函數(shù)形式寫法 var compose = (...fns) => result => fns.reverse().reduce( (result,fn) => fn( result ) , result );
注意到 reduce(..) 循環(huán)發(fā)生在最后的 composed(..) 運(yùn)行時(shí),并且每一個(gè)中間的 result(..) 將會(huì)在下一次調(diào)用時(shí)作為輸入值傳遞給下一個(gè)迭代。
這種實(shí)現(xiàn)的優(yōu)點(diǎn)就是代碼更簡(jiǎn)練,并且使用了常見(jiàn)的函數(shù)式編程結(jié)構(gòu):reduce(..)。這種實(shí)現(xiàn)方式的性能和原始的 for 循環(huán)版本很相近。
但是,這種實(shí)現(xiàn)局限處在于外層的組合函數(shù)(也就是,組合中的第一個(gè)函數(shù))只能接收一個(gè)參數(shù)。其他大多數(shù)實(shí)現(xiàn)在首次調(diào)用的時(shí)候就把所有參數(shù)傳進(jìn)去了。如果組合中的每一個(gè)函數(shù)都是一元的,這個(gè)方案沒(méi)啥大問(wèn)題。但如果你需要給第一個(gè)調(diào)用傳遞多參數(shù),那么你可能需要不同的實(shí)現(xiàn)方案。
為了修正第一次調(diào)用的單參數(shù)限制,我們可以仍使用 reduce(..) ,但加一個(gè)懶執(zhí)行函數(shù)包裹器:
function compose(...fns) { return fns.reverse().reduce( function reducer(fn1,fn2){ return function composed(...args){ return fn2( fn1( ...args ) ); }; } ); } // ES6 箭頭函數(shù)形式寫法 var compose = (...fns) => fns.reverse().reduce( (fn1,fn2) => (...args) => fn2( fn1( ...args ) ) );
注意到我們直接返回了 reduce(..) 調(diào)用的結(jié)果,該結(jié)果自身就是個(gè)函數(shù),不是一個(gè)計(jì)算過(guò)的值。該函數(shù)讓我們能夠傳入任意數(shù)目的參數(shù),在整個(gè)組合過(guò)程中,將這些參數(shù)傳入到第一個(gè)函數(shù)調(diào)用中,然后依次產(chǎn)出結(jié)果給到后面的調(diào)用。
相較于直接計(jì)算結(jié)果并把它傳入到 reduce(..) 循環(huán)中進(jìn)行處理,這種實(shí)現(xiàn)通過(guò)在組合之前只運(yùn)行 一次 reduce(..) 循環(huán),然后將所有的函數(shù)調(diào)用運(yùn)算全部延遲了 ———— 稱為惰性運(yùn)算。每一個(gè)簡(jiǎn)化后的局部結(jié)果都是一個(gè)包裹層級(jí)更多的函數(shù)。
當(dāng)你調(diào)用最終組合函數(shù)并且提供一個(gè)或多個(gè)參數(shù)的時(shí)候,這個(gè)層層嵌套的大函數(shù)內(nèi)部的所有層級(jí),由內(nèi)而外調(diào)用,以相反的方式連續(xù)執(zhí)行(不是通過(guò)循環(huán))。
這個(gè)版本的性能特征和之前 reduce(..) 基礎(chǔ)實(shí)現(xiàn)版有潛在的差異。在這兒,reduce(..) 只在生成大個(gè)的組合函數(shù)時(shí)運(yùn)行過(guò)一次,然后這個(gè)組合函數(shù)只是簡(jiǎn)單的一層層執(zhí)行它內(nèi)部所嵌套的函數(shù)。在前一版本中,reduce(..) 將在每一次調(diào)用中運(yùn)行。
在考慮哪一種實(shí)現(xiàn)更好時(shí),你的情況可能會(huì)不一樣,但是要記得后面的實(shí)現(xiàn)方式并沒(méi)有像前一種限制只能傳一個(gè)參數(shù)。
我們也能夠使用遞歸來(lái)定義 compose(..)。遞歸式定義的 compose(fn1,fn2, .. fnN) 看起來(lái)會(huì)是這樣:
compose( compose(fn1,fn2, .. fnN-1), fnN );
注意: 我們將在第 9 章揭示更多的細(xì)節(jié),所以如果這塊看起來(lái)讓你疑惑,那么暫時(shí)跳過(guò)該部分是沒(méi)問(wèn)題的,你可以在閱讀完第 9 章后再來(lái)看。
這里是我們用遞歸實(shí)現(xiàn) compose(..) 的代碼:
function compose(...fns) { // 拿出最后兩個(gè)參數(shù) var [ fn1, fn2, ...rest ] = fns.reverse(); var composedFn = function composed(...args){ return fn2( fn1( ...args ) ); }; if (rest.length == 0) return composedFn; return compose( ...rest.reverse(), composedFn ); } // ES6 箭頭函數(shù)形式寫法 var compose = (...fns) => { // 拿出最后兩個(gè)參數(shù) var [ fn1, fn2, ...rest ] = fns.reverse(); var composedFn = (...args) => fn2( fn1( ...args ) ); if (rest.length == 0) return composedFn; return compose( ...rest.reverse(), composedFn ); };
我認(rèn)為遞歸實(shí)現(xiàn)的好處是更加概念化。我個(gè)人覺(jué)得相較于不得不在循環(huán)里跟蹤運(yùn)行結(jié)果,通過(guò)遞歸的方式進(jìn)行重復(fù)的動(dòng)作反而更易懂。所以我更喜歡以這種方式的代碼來(lái)表達(dá)。
其他人可能會(huì)覺(jué)得遞歸的方法在智力上造成的困擾更讓人有些畏懼。我建議你作出自己的評(píng)估。
重排序組合我們?cè)缙谡劶暗氖菑挠彝箜樞虻臉?biāo)準(zhǔn) compose(..) 實(shí)現(xiàn)。這么做的好處是能夠和手工組合列出參數(shù)(函數(shù))的順序保持一致。
不足之處就是它們排列的順序和它們執(zhí)行的順序是相反的,這將會(huì)造成困擾。同時(shí),不得不使用 partialRight(compose, ..) 提早定義要在組合過(guò)程中 第一個(gè) 執(zhí)行的函數(shù)。
相反的順序,從右往左的組合,有個(gè)常見(jiàn)的名字:pipe(..)。這個(gè)名字據(jù)說(shuō)來(lái)自 Unix/Linux 界,那里大量的程序通過(guò)“管道傳輸”(| 運(yùn)算符)第一個(gè)的輸出到第二個(gè)的輸入,等等(即,ls -la | grep "foo" | less)。
pipe(..) 與 compose(..) 一模一樣,除了它將列表中的函數(shù)從左往右處理。
function pipe(...fns) { return function piped(result){ var list = fns.slice(); while (list.length > 0) { // 從列表中取第一個(gè)函數(shù)并執(zhí)行 result = list.shift()( result ); } return result; }; }
實(shí)際上,我們只需將 compose(..) 的參數(shù)反轉(zhuǎn)就能定義出來(lái)一個(gè) pipe(..)。
var pipe = reverseArgs( compose );
非常簡(jiǎn)單!
回憶下之前的通用組合的例子:
var biggerWords = compose( skipShortWords, unique, words );
以 pipe(..) 的方式來(lái)實(shí)現(xiàn),我們只需要反轉(zhuǎn)參數(shù)的順序:
var biggerWords = pipe( words, unique, skipShortWords );
pipe(..) 的優(yōu)勢(shì)在于它以函數(shù)執(zhí)行的順序排列參數(shù),某些情況下能夠減輕閱讀者的疑惑。pipe(words,unique,skipShortWords) 看起來(lái)和讀起來(lái)會(huì)更簡(jiǎn)單,能知道我們首先執(zhí)行 words(..),然后 unique(..),最后是 skipShortWords(..)。
假如你想要部分的應(yīng)用第一個(gè)函數(shù)(們)來(lái)負(fù)責(zé)執(zhí)行,pipe(..) 同樣也很方便。就像我們之前使用 compose(..) 構(gòu)建的右偏函數(shù)應(yīng)用一樣。
對(duì)比:
var filterWords = partialRight( compose, unique, words ); // vs var filterWords = partial( pipe, words, unique );
你可能會(huì)回想起第 3 章 partialRight(..) 中的定義,它實(shí)際使用了 reverseArgs(..),就像我們的 pipe(..) 現(xiàn)在所做的。所以,不管怎樣,我們得到了同樣的結(jié)果。
在這一特定場(chǎng)景下使用 pipe(..) 的輕微性能優(yōu)勢(shì)在于我們不必再通過(guò)右偏函數(shù)應(yīng)用的方式來(lái)使用 compose(..) 保存從右往左的參數(shù)順序,使用
pipe(..) 我們不必再跟 partialRight(..) 一樣需要將參數(shù)順序反轉(zhuǎn)回去。所以在這里 partial(pipe, ..) 比 partialRight(compose, ..) 要好一點(diǎn)。
一般來(lái)說(shuō),在使用一個(gè)完善的函數(shù)式編程庫(kù)時(shí),pipe(..) 和 compose(..) 沒(méi)有明顯的性能區(qū)別。
抽象抽象經(jīng)常被定義為對(duì)兩個(gè)或多個(gè)任務(wù)公共部分的剝離。通用部分只定義一次,從而避免重復(fù)。為了展現(xiàn)每個(gè)任務(wù)的特殊部分,通用部分需要被參數(shù)化。
舉個(gè)例子,思考如下(明顯刻意生成的)代碼:
function saveComment(txt) { if (txt != "") { comments[comments.length] = txt; } } function trackEvent(evt) { if (evt.name !== undefined) { events[evt.name] = evt; } }
這兩個(gè)實(shí)用函數(shù)都是將一個(gè)值存入一個(gè)數(shù)據(jù)源,這是通用的部分。不同的是一個(gè)是將值放置到數(shù)組的末尾,另一個(gè)是將值放置到對(duì)象的某個(gè)屬性上。
讓我們抽象一下:
function storeData(store,location,value) { store[location] = value; } function saveComment(txt) { if (txt != "") { storeData( comments, comments.length, txt ); } } function trackEvent(evt) { if (evt.name !== undefined) { storeData( events, evt.name, evt ); } }
引用一個(gè)對(duì)象(或數(shù)組,多虧了 JS 中方便的 [] 符號(hào))屬性和將值設(shè)入的通用任務(wù)被抽象到獨(dú)立的 storeData(..) 函數(shù)。這個(gè)函數(shù)當(dāng)前只有一行代碼,該函數(shù)能提出其它多任務(wù)中通用的行為,比如生成唯一的數(shù)字 ID 或?qū)r(shí)間戳存入。
如果我們?cè)诙嗵幹貜?fù)通用的行為,我們將會(huì)面臨改了幾處但忘了改別處的維護(hù)風(fēng)險(xiǎn)。在做這類抽象時(shí),有一個(gè)原則是,通常被稱作 DRY(don"t repeat yourself)。
DRY 力求能在程序的任何任務(wù)中有唯一的定義。代碼不夠 DRY 的另一個(gè)托辭就是程序員們太懶,不想做非必要的工作。
抽象能夠走得更遠(yuǎn)。思考:
function conditionallyStoreData(store,location,value,checkFn) { if (checkFn( value, store, location )) { store[location] = value; } } function notEmpty(val) { return val != ""; } function isUndefined(val) { return val === undefined; } function isPropUndefined(val,obj,prop) { return isUndefined( obj[prop] ); } function saveComment(txt) { conditionallyStoreData( comments, comments.length, txt, notEmpty ); } function trackEvent(evt) { conditionallyStoreData( events, evt.name, evt, isPropUndefined ); }
為了實(shí)現(xiàn) DRY 和避免重復(fù)的 if 語(yǔ)句,我們將條件判斷移動(dòng)到了通用抽象中。我們同樣假設(shè)在程序中其它地方可能會(huì)檢查非空字符串或非 undefined 的值,所以我們也能將這些東西 DRY 出來(lái)。
這些代碼現(xiàn)在變得更 DRY 了,但有些抽象過(guò)度了。開(kāi)發(fā)者需要對(duì)他們程序中每個(gè)部分使用恰當(dāng)?shù)某橄蠹?jí)別保持謹(jǐn)慎,不能太過(guò),也不能不夠。
關(guān)于我們?cè)诒菊轮袑?duì)函數(shù)的組合進(jìn)行的大量討論,看起來(lái)它的好處是實(shí)現(xiàn)這種 DRY 抽象。但讓我們別急著下結(jié)論,因?yàn)槲艺J(rèn)為組合實(shí)際上在我們的代碼中發(fā)揮著更重要的作用。
而且,即使某些東西只出現(xiàn)了一次,組合仍然十分有用 (沒(méi)有重復(fù)的東西可以被抽出來(lái))。
除了通用化和特殊化的對(duì)比,我認(rèn)為抽象有更多有用的定義,正如下面這段引用所說(shuō):
... 抽象是一個(gè)過(guò)程,程序員將一個(gè)名字與潛在的復(fù)雜程序片段關(guān)聯(lián)起來(lái),這樣該名字就能夠被認(rèn)為代表函數(shù)的目的,而不是代表函數(shù)如何實(shí)現(xiàn)的。通過(guò)隱藏?zé)o關(guān)的細(xì)節(jié),抽象降低了概念復(fù)雜度,讓程序員在任意時(shí)間都可以集中注意力在程序內(nèi)容中的可維護(hù)子集上。
《程序設(shè)計(jì)語(yǔ)言》, 邁克爾 L 斯科特
https://books.google.com/book...
// TODO: 給這本書或引用弄一個(gè)更好的參照,至少找到一個(gè)更好的在線鏈接
這段引用表述的觀點(diǎn)是抽象 ———— 通常來(lái)說(shuō),是指把一些代碼片段放到自己的函數(shù)中 ———— 是圍繞著能將兩部分功能分離,從而達(dá)到可以專注于某一獨(dú)立的部分為主要目的來(lái)服務(wù)的。
需要注意的是,這種場(chǎng)景下的抽象并不是為了隱藏細(xì)節(jié),比如把一些東西當(dāng)作黑盒來(lái)對(duì)待。這一觀念其實(shí)更貼近于編程中的封裝性原則。我們不是為了隱藏細(xì)節(jié)而抽象,而是為了通過(guò)分離來(lái)突出關(guān)注點(diǎn)。
還記得這段文章的開(kāi)頭,我說(shuō)函數(shù)式編程的目的是為了創(chuàng)造更可讀、更易理解的代碼。一個(gè)有效的方法是將交織纏繞的 ———— 緊緊編織在一起,像一股繩子 ———— 代碼解綁為分離的、更簡(jiǎn)單的 ———— 松散綁定的 ———— 代碼片段。以這種方式來(lái)做的話,代碼的閱讀者將不會(huì)在尋找其它部分細(xì)節(jié)的時(shí)候被其中某塊的細(xì)節(jié)所分心。
我們更高的目標(biāo)是不只對(duì)某些東西實(shí)現(xiàn)一次,這是 DRY 的觀念。實(shí)際上,有些時(shí)候我們確實(shí)在代碼中不斷重復(fù)。于是,我們尋求更分離的實(shí)現(xiàn)方式。我們嘗試突出關(guān)注點(diǎn),因?yàn)檫@能提高可讀性。
另一種描述這個(gè)目標(biāo)的方式就是 ———— 通過(guò)命令式 vs 聲明式的編程風(fēng)格。命令式代碼主要關(guān)心的是描述怎么做來(lái)準(zhǔn)確完成一項(xiàng)任務(wù)。聲明式代碼則是描述輸出應(yīng)該是什么,并將具體實(shí)現(xiàn)交給其它部分。
換句話說(shuō),聲明式代碼從怎么做中抽象出了是什么。盡管普通的聲明式代碼在可讀性上強(qiáng)于命令式,但沒(méi)有程序(除了機(jī)器碼 1 和 0)是完全的聲明式或者命令式代碼。編程者必須在它們之間尋找平衡。
ES6 增加了很多語(yǔ)法功能,能將老的命令式操作轉(zhuǎn)換為新的聲明式形式。可能最清晰的當(dāng)屬解構(gòu)了。解構(gòu)是一種賦值模式,它描述了如何將組合值(對(duì)象、數(shù)組)內(nèi)的構(gòu)成值分解出來(lái)的方法。
這里是一個(gè)數(shù)組解構(gòu)的例子:
function getData() { return [1,2,3,4,5]; } // 命令式 var tmp = getData(); var a = tmp[0]; var b = tmp[3]; // 聲明式 var [ a ,,, b ] = getData();
是什么就是將數(shù)組中的第一個(gè)值賦給 a,然后第四個(gè)值賦給 b。怎么做就是得到一個(gè)數(shù)組的引用(tmp)然后手動(dòng)的通過(guò)數(shù)組索引 0 和 3,分別賦值給 a 和 b。
數(shù)組的解構(gòu)是否隱藏了賦值細(xì)節(jié)?這要看你看待的角度了。我認(rèn)為它知識(shí)簡(jiǎn)單的將是什么從怎么做中分離出來(lái)。JS 引擎仍然做了賦值的工作,但它阻止了你自己去抽象怎么做的過(guò)程。
相反的是,你閱讀 [ a ,,, b ] = .. 的時(shí)候,便能看到該賦值模式只不過(guò)是告訴你將要發(fā)生的是什么。數(shù)組的解構(gòu)是聲明式抽象的一個(gè)例子。
將組合當(dāng)作抽象函數(shù)組合到底做了什么?函數(shù)組合同樣也是一種聲明式抽象。
回想下之前的 shorterWords(..) 例子。讓我們對(duì)比下命令式和聲明式的定義。
// 命令式 function shorterWords(text) { return skipLongWords( unique( words( text ) ) ); } // 聲明式 var shorterWords = compose( skipLongWords, unique, words );
聲明式關(guān)注點(diǎn)在是什么上 -- 這 3 個(gè)函數(shù)傳遞的數(shù)據(jù)從一個(gè)字符串到一系列更短的單詞 -- 并且將怎么做留在了 compose(..)的內(nèi)部。
在一個(gè)更大的層面上看,shorterWords = compose(..) 行解釋了怎么做來(lái)定義一個(gè) shorterWords(..) 實(shí)用函數(shù),這樣在代碼的別處使用時(shí),只需關(guān)注下面這行聲明式的代碼輸出是什么。
shorterWords( text );
組合將一步步得到一系列更短的單詞的過(guò)程抽象了出來(lái)。
相反的看,如果我們不使用組合抽象呢?
var wordsFound = words( text ); var uniqueWordsFound = unique( wordsFound ); skipLongWords( uniqueWordsFound );
或者這種:
skipLongWords( unique( words( text ) ) );
這兩個(gè)版本展示的都是一種更加命令式的風(fēng)格,違背了聲明式風(fēng)格優(yōu)先原則。閱讀者關(guān)注這兩個(gè)代碼片段時(shí),會(huì)被更多的要求了解怎么做而不是是什么。
函數(shù)組合并不是通過(guò) DRY 的原則來(lái)節(jié)省代碼量。即使 shorterWords(..) 的使用只出現(xiàn)了一次 -- 所以并沒(méi)有重復(fù)問(wèn)題需要避免!-- 從怎么做中分離出是什么仍能幫助我們提升代碼。
組合是一個(gè)抽象的強(qiáng)力工具,它能夠?qū)⒚钍酱a抽象為更可讀的聲明式代碼。
回顧形參既然我們已經(jīng)把組合都了解了一遍 -- 那么是時(shí)候拋出函數(shù)式編程中很多地方都有用的小技巧了 -- 讓我們通過(guò)在某個(gè)場(chǎng)景下回顧第 3 章的“無(wú)形參”(譯者注:“無(wú)形參”指的是移除對(duì)函數(shù)形參的引用)段落中的 point-free 代碼,并把它重構(gòu)的稍微復(fù)雜點(diǎn)來(lái)觀察這種小技巧。
// 提供該API:ajax( url, data, cb ) var getPerson = partial( ajax, "http://some.api/person" ); var getLastOrder = partial( ajax, "http://some.api/order", { id: -1 } ); getLastOrder( function orderFound(order){ getPerson( { id: order.personId }, function personFound(person){ output( person.name ); } ); } );
我們想要移除的“點(diǎn)”是對(duì) order 和 person 參數(shù)的引用。
讓我們嘗試將 person 形參移出 personFound(..) 函數(shù)。要達(dá)到目的,我們需要首先定義:
function extractName(person) { return person.name; }
但據(jù)我們觀察這段操作能夠表達(dá)的更通用些:將任意對(duì)象的任意屬性通過(guò)屬性名提取出來(lái)。讓我們把這個(gè)實(shí)用函數(shù)稱為 prop(..):
function prop(name,obj) { return obj[name]; } // ES6 箭頭函數(shù)形式 var prop = (name,obj) => obj[name];
我們處理對(duì)象屬性的時(shí)候,也需要定義下反操作的工具函數(shù):setProp(..),為了將屬性值設(shè)到某個(gè)對(duì)象上。
但是,我們想小心一些,不改動(dòng)現(xiàn)存的對(duì)象,而是創(chuàng)建一個(gè)攜帶變化的復(fù)制對(duì)象,并將它返回出去。這樣處理的原因?qū)⒃诘?5 章中討論更多細(xì)節(jié)。
function setProp(name,obj,val) { var o = Object.assign( {}, obj ); o[name] = val; return o; }
現(xiàn)在,定義一個(gè) extractName(..) ,它能將對(duì)象中的 "name" 屬性拿出來(lái),我們將部分應(yīng)用 prop(..):
var extractName = partial( prop, "name" );
注意: 不要誤解這里的 extractName(..),它其實(shí)什么都還沒(méi)有做。我們只是部分應(yīng)用 prop(..) 來(lái)創(chuàng)建了一個(gè)等待接收包含 "name"屬性的對(duì)象的函數(shù)。我們也能通過(guò)curry(prop)("name")做到一樣的事。
下一步,讓我們縮小關(guān)注點(diǎn),看下例子中嵌套的這塊查找操作的調(diào)用:
getLastOrder( function orderFound(order){ getPerson( { id: order.personId }, outputPersonName ); } );
我們?cè)撊绾味x outputPersonName(..)?為了方便形象化我們所需要的東西,想一下我們需要的數(shù)據(jù)流是什么樣:
output <-- extractName <-- person
outputPersonName(..) 需要是一個(gè)接收(對(duì)象)值的函數(shù),并將它傳遞給 extractName(..),然后將處理后的值傳給 output(..)。
希望你能看出這里需要 compose(..) 操作。所以我們能夠?qū)?outputPersonName(..) 定義為:
var outputPersonName = compose( output, extractName );
我們剛剛創(chuàng)建的 outputPersonName(..) 函數(shù)是提供給 getPerson(..) 的回調(diào)。所以我們還能定義一個(gè)函數(shù)叫做 processPerson(..) 來(lái)處理回調(diào)參數(shù),使用 partialRight(..):
var processPerson = partialRight( getPerson, outputPersonName );
讓我們用新函數(shù)來(lái)重構(gòu)下之前的代碼:
getLastOrder( function orderFound(order){ processPerson( { id: order.personId } ); } );
唔,進(jìn)展還不錯(cuò)!
但我們需要繼續(xù)移除掉 order 這個(gè)“形參”。下一步是觀察 personId 能夠被 prop(..) 從一個(gè)對(duì)象(比如 order)中提取出來(lái),就像我們?cè)?person 對(duì)象中提取 name 一樣。
var extractPersonId = partial( prop, "personId" );
為了創(chuàng)建傳遞給 processPerson(..) 的對(duì)象( { id: .. } 的形式),讓我們創(chuàng)建一個(gè)實(shí)用函數(shù) makeObjProp(..),用來(lái)以特定的屬性名將值包裝為一個(gè)對(duì)象。
function makeObjProp(name,value) { return setProp( name, {}, value ); } // ES6 箭頭函數(shù)形式 var makeObjProp = (name,value) => setProp( name, {}, value );
提示: 這個(gè)實(shí)用函數(shù)在 Ramda 庫(kù)中被稱為 objOf(..)。
就像我們之前使用 prop(..) 來(lái)創(chuàng)建 extractName(..),我們將部分應(yīng)用 makeObjProp(..) 來(lái)創(chuàng)建 personData(..) 函數(shù)用來(lái)制作我們的數(shù)據(jù)對(duì)象。
var personData = partial( makeObjProp, "id" );
為了使用 processPerson(..) 來(lái)完成通過(guò) order 值查找一個(gè)人的功能,我們需要的數(shù)據(jù)流如下:
processPerson <-- personData <-- extractPersonId <-- order
所以我們只需要再使用一次 compose(..) 來(lái)定義一個(gè) lookupPerson(..) :
var lookupPerson = compose( processPerson, personData, extractPersonId );
然后,就是這樣了!把這整個(gè)例子重新組合起來(lái),不帶任何的“形參”:
var getPerson = partial( ajax, "http://some.api/person" ); var getLastOrder = partial( ajax, "http://some.api/order", { id: -1 } ); var extractName = partial( prop, "name" ); var outputPersonName = compose( output, extractName ); var processPerson = partialRight( getPerson, outputPersonName ); var personData = partial( makeObjProp, "id" ); var extractPersonId = partial( prop, "personId" ); var lookupPerson = compose( processPerson, personData, extractPersonId ); getLastOrder( lookupPerson );
哇哦。沒(méi)有形參。并且 compose(..) 在兩處地方看起來(lái)相當(dāng)有用!
我認(rèn)為在這樣的場(chǎng)景下,即使推導(dǎo)出我們最終答案的步驟有些多,但最終的代碼卻變得更加可讀,因?yàn)槲覀儾挥迷偃ピ敿?xì)的調(diào)用每一步了。
即使你不想看到或命名這么多中間步驟,你依然可以通過(guò)不使用獨(dú)立變量而是將表達(dá)式串起來(lái)來(lái)來(lái)保留無(wú)點(diǎn)特性。
partial( ajax, "http://some.api/order", { id: -1 } ) ( compose( partialRight( partial( ajax, "http://some.api/person" ), compose( output, partial( prop, "name" ) ) ), partial( makeObjProp, "id" ), partial( prop, "personId" ) ) );
這段代碼肯定沒(méi)那么羅嗦了,但我認(rèn)為比之前的每個(gè)操作都有其對(duì)應(yīng)的變量相比,可讀性略有降低。但是不管怎樣,組合幫助我們實(shí)現(xiàn)了無(wú)點(diǎn)的風(fēng)格。
總結(jié)函數(shù)組合是一種定義函數(shù)的模式,它能將一個(gè)函數(shù)調(diào)用的輸出路由到另一個(gè)函數(shù)的調(diào)用上,然后一直進(jìn)行下去。
因?yàn)?JS 函數(shù)只能返回單個(gè)值,這個(gè)模式本質(zhì)上要求所有組合中的函數(shù)(可能第一個(gè)調(diào)用的函數(shù)除外)是一元的,當(dāng)前函數(shù)從上一個(gè)函數(shù)輸出中只接收一個(gè)輸入。
相較于在我們的代碼里詳細(xì)列出每個(gè)調(diào)用,函數(shù)組合使用 compose(..) 實(shí)用函數(shù)來(lái)提取出實(shí)現(xiàn)細(xì)節(jié),讓代碼變得更可讀,讓我們更關(guān)注組合完成的是什么,而不是它具體做什么。
組合 ———— 聲明式數(shù)據(jù)流 ———— 是支撐函數(shù)式編程其他特性的最重要的工具之一。
【上一章】翻譯連載 | JavaScript 輕量級(jí)函數(shù)式編程-第3章:管理函數(shù)的輸入 |《你不知道的JS》姊妹篇
【下一章】翻譯連載 | JavaScript輕量級(jí)函數(shù)式編程-第5章:減少副作用 |《你不知道的JS》姊妹篇
iKcamp原創(chuàng)新書《移動(dòng)Web前端高效開(kāi)發(fā)實(shí)戰(zhàn)》已在亞馬遜、京東、當(dāng)當(dāng)開(kāi)售。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/85185.html
摘要:我稱之為輕量級(jí)函數(shù)式編程。序眾所周知,我是一個(gè)函數(shù)式編程迷。函數(shù)式編程有很多種定義。本書是你開(kāi)啟函數(shù)式編程旅途的絕佳起點(diǎn)。事實(shí)上,已經(jīng)有很多從頭到尾正確的方式介紹函數(shù)式編程的書了。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團(tuán)隊(duì)(排名不分先后):阿希、blueken、brucecham、...
摘要:所以我覺(jué)得函數(shù)式編程領(lǐng)域更像學(xué)者的領(lǐng)域。函數(shù)式編程的原則是完善的,經(jīng)過(guò)了深入的研究和審查,并且可以被驗(yàn)證。函數(shù)式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數(shù)式編程編程者會(huì)認(rèn)為形式主義本身有助于學(xué)習(xí)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液...
摘要:一旦我們滿足了基本條件值為,我們將不再調(diào)用遞歸函數(shù),只是有效地執(zhí)行了。遞歸深諳函數(shù)式編程之精髓,最被廣泛引證的原因是,在調(diào)用棧中,遞歸把大部分顯式狀態(tài)跟蹤換為了隱式狀態(tài)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;...
摘要:從某些方面來(lái)講,這章回顧的函數(shù)知識(shí)并不是針對(duì)函數(shù)式編程者,非函數(shù)式編程者同樣需要了解。什么是函數(shù)針對(duì)函數(shù)式編程,很自然而然的我會(huì)想到從函數(shù)開(kāi)始。如果你計(jì)劃使用函數(shù)式編程,你應(yīng)該盡可能多地使用函數(shù),而不是程序。指的是一個(gè)函數(shù)聲明的形參數(shù)量。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關(guān)于譯者:...
摘要:本書主要探索函數(shù)式編程的核心思想。我們?cè)谥袘?yīng)用的僅僅是一套基本的函數(shù)式編程概念的子集。我稱之為輕量級(jí)函數(shù)式編程。通常來(lái)說(shuō),關(guān)于函數(shù)式編程的書籍都熱衷于拓展閱讀者的知識(shí)面,并企圖覆蓋更多的知識(shí)點(diǎn)。,本書統(tǒng)稱為函數(shù)式編程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團(tuán)隊(duì)(排名不分先后)...
閱讀 2423·2021-10-09 09:59
閱讀 2177·2021-09-23 11:30
閱讀 2591·2019-08-30 15:56
閱讀 1145·2019-08-30 14:00
閱讀 2939·2019-08-29 12:37
閱讀 1253·2019-08-28 18:16
閱讀 1656·2019-08-27 10:56
閱讀 1022·2019-08-26 17:23