高級函數(shù)式編程:使用流管道優(yōu)化數(shù)據(jù)處理性能
先決條件
牢牢掌握 ES6,尤其是箭頭函數(shù)、生成器、Array.map、Array.reduce ……
對柯里化、純函數(shù)、高階函數(shù)等函數(shù)式編程有深刻的理解,
RxJS 的基礎(chǔ)知識。
的注意:本文著重于高級函數(shù)式編程,因此請確保您在深入了解主要內(nèi)容之前滿足要求。
目錄
一、 功能構(gòu)成
2. 換能器
3. RxJS 用例
功能組合
什么是函數(shù)組合,我們?yōu)槭裁葱枰?/span>
在計算機科學(xué)中,函數(shù)組合是一種組合簡單功能以構(gòu)建更復(fù)雜功能的行為或機制
組合函數(shù)可幫助開發(fā)人員將復(fù)雜的邏輯分解為一組較小的問題,從而顯著提高可維護(hù)性和代碼重用性。
一個真實世界的例子來進(jìn)一步理解:
想象一下,您是一名調(diào)酒師,其工作是以有序的方式混合烈酒、飲料和許多其他類型的配料來提供雞尾酒。
你有一個空杯子 讓 glass = "" 作為初始值和多個準(zhǔn)備步驟:
請記住,成分和您采取的步驟很重要,因為它會產(chǎn)生不同的飲料:
帕洛瑪雞尾酒配方
對于每份雞尾酒配方,我們都需要編寫一個函數(shù)來準(zhǔn)備它,其中包含需要時間執(zhí)行的各種準(zhǔn)備步驟。所以你開始有了一個想法:實現(xiàn)一個樸素的let 循環(huán),以有序的方式執(zhí)行準(zhǔn)備步驟。
更短,更容易閱讀!
如同數(shù)學(xué)中的函數(shù)組合,每個函數(shù)的結(jié)果作為下一個函數(shù)的參數(shù)傳遞,最后一個函數(shù)的結(jié)果是整體的結(jié)果。
這種命令式實現(xiàn)在函數(shù)組合方面已經(jīng)足夠好了,但因為我們正在學(xué)習(xí)函數(shù)式編程,所以讓我們通過使用Arrray.reduce 來簡化serveCocktail 函數(shù),使其成為聲明式的:
onst serveCocktail = (...方法) => {
// 初始值被硬編碼為空字符串
return methods.reduce((glass, method) => 方法(glass), "")}
更好的是,我們可以使用柯里化來提升初始值作為參數(shù)。對于通用用途,我們定義了一個實用函數(shù),使我們可以輕松地組合函數(shù)。
pipe : 從左到右組合函數(shù)。
compose : 從右到左組合函數(shù)。
在pipe和 compose的幫助下,組合函數(shù)要容易得多 。
當(dāng)我們將一個復(fù)雜的過程分解成更小的函數(shù)時,每個函數(shù)只做一項工作,開發(fā)人員可以編寫聲明性代碼來增強可讀性、可重用性和關(guān)注點分離。
傳感器
transducer 是一個可組合的高階 reducer。它以一個 reducer 作為輸入,并返回另一個 reducer(埃里克·埃利奧特)
reducer是一個純函數(shù),它接受 2 個參數(shù)并返回一個新值。
在循環(huán)上下文中應(yīng)用reducer時:
名為accumulator的第一個參數(shù)是上一步的結(jié)果,如果它是循環(huán)中的第一步,則為初始值。
第二個參數(shù)是循環(huán)中的當(dāng)前值。
所以reducer的簡單語法如下:
reducer = (accumulator, curVal) => newVal
然后 transducer 的語法可以表示為:
換能器 = 減速器 => 減速器
回答“為什么我們需要換能器?”這個問題 ,跟我舉個例子:
想象一下,作為 NASA 的一名科學(xué)家,你的職責(zé)是通過接收信號與我們星球外的外星飛船進(jìn)行通信,并將其轉(zhuǎn)換為人類可理解的數(shù)據(jù),然后顯示最終信息。
轉(zhuǎn)換信號:過濾虛假信號(噪聲),然后將信號轉(zhuǎn)換為字符串。
顯示消息:連接字符串。
只有當(dāng)你的數(shù)組很小時,這個簡單但天真的實現(xiàn)才合適。
考慮處理大量的萬億信號:
filter
每次使用and遍歷數(shù)組時map
,JavaScript 都會啟動一個全新的中間數(shù)組,它會占用大量內(nèi)存。在執(zhí)行下一個之前,您需要等待每個處理步驟完成。在上面的代碼中,如果我們要檢索最終的消息,我們必須等待
reduce
它完成。在此之前,reduce
必須等待map
完成。在此之前,map
必須等待filter
完成。這是一個致命的問題,因為消息中的信息非常關(guān)鍵,我們必須在非常嚴(yán)格的期限內(nèi)閱讀它。
解決這個問題的一種方法是構(gòu)建一個處理管道,將信號作為數(shù)據(jù)流,并以正確的順序?qū)γ總€信號執(zhí)行。
稍微重寫一下,以便您可以識別流程xform
與函數(shù)組合完全相同。的結(jié)果decodeSignal
取決于isTruthful
,而 的結(jié)果concatString
取決于decodeSignal
。
(味精,信號)=> {
讓 val = isTruthful(sig) ? 簽名:空
如果(值){
val = decodeSignal(val)
val = concatString(msg, val)
返回值
}
返回消息}
在這個例子中,我們只需要處理幾個處理步驟。但請記住,在現(xiàn)實世界中,信號轉(zhuǎn)換過程必須以復(fù)雜的順序經(jīng)歷多個步驟。為了構(gòu)建靈活的管道,我們開始想到函數(shù)組合:
撰寫(isTruthful,decodeSignal,concatString)//不工作
但它不會起作用,僅僅是因為這些函數(shù)不可組合。換句話說,前一個函數(shù)的結(jié)果與下一個函數(shù)的參數(shù)不兼容。
為了使鏈接成為可能,我們必須重寫 filter
and map
函數(shù):
const filter = next => {
返回 (msg, sig) => isTruthful(sig) ?下一個(味精,信號):味精} const map = next => {
返回(味精,信號)=>下一個(味精,解碼信號(信號))}
假設(shè)那next
是一個reducer,那么filter
和map
是transducer,它們將一個reducer作為輸入并返回另一個reducer。
為了增強可重用性,我們使用柯里化來提升isTruthful 和decodeSignal 作為參數(shù):
const filter = predicate => next =>
(acc, cur) =>謂詞(cur) ? 下一個(acc,cur):acc const map = transform => next =>
(acc, cur) => next(acc, transform (cur)) const transformSignals = compose ( filter ( isTruthful ), map ( decodeSignal )}
我們可以使用compose
組合 傳感器 ,因為它們是可組合的,結(jié)果 transformSignals 是一個新的 傳感器,所以我們必須提供一個縮減器作為最后一步,告訴 傳感器 如何累積結(jié)果。
const xform = transformSignals( concatString ) const message = signals.reduce( xform , "")
總結(jié)我們剛剛所做的一切,這是您可以運行的完整代碼:
RxJS 用例
恭喜?。?!如果您正在閱讀本節(jié),那么您已經(jīng)完成了所有最難的部分。RxJS 是一個支持流數(shù)據(jù)的庫,它有許多類似的轉(zhuǎn)換器filter
,map
因此我們不必從頭開始實現(xiàn)任何東西。
嘗試運行并觀察輸出,可以清楚地看到每個信號都是獨立處理的,并立即打印出結(jié)果。