函數式編程中的數組問題

這裏只傳授最高端的編程技巧...

好久沒講技術了,先回憶一下啥是函數式編程(FP)吧,比如FP要求使用表達式,不允許出現語句,這樣更接近自然語言。



表達式取代經典語句

什麼叫語句呢?學校編程課本上教的變量聲明語句,循環語句,條件判斷語句,枚舉語句,這些都是語句,也就是說我們再熟悉不過的if/else語句,for/while循環,switch以及try/catch都不給用了!

沒有這些語句還編個P程啊?我當時也有一種“這些年編程白學了”的衝動,雖然官方說每一種語句都可以用對應的表達式來替代,比如在JavaScript領域,變量聲明省略掉關鍵詞後就變成了表達式:

  • 變量聲明語句

// 變量聲明語句+賦值let test = 123;
// 變量申明+賦值表達式test = 123;

因爲變量總是屬於當前函數的變量對象(variable object),聲明變量等同於給對象添加屬性,所以變量申明表達式返回賦的值或者undefined。

  • if/else語句

函數式替換if/else語句也很簡單,我們本來就有條件運算符(… ? … : …)可用:

// 條件語句if(convention){}else {}
// 條件表達式convention ? expression1 : expression2;
  • switch語句

switch語句的話可以用js散列表來模擬,也就是對象,用散列表來枚舉比switch遍歷快許多

// 狀態枚舉語句switch (expression) {  case value1:    break;  case value2:    break;  default:    break;}
// 字典表達式({  value1(){},  value2(){},})[expression] || default();
  • try&catch語句

至於try/catch/finally可以將同步流包裹進promise,再給他監聽一個catch方法:

// 異常處理語句try{  // 代碼塊}catch(err){  }finally{}
// 異常處理表達式new Promise((res,rej)=>{  // 代碼塊}).catch(err=>{ }).finally(()=>{})

以上這些表達式都完美替換了經典語句,但是我在“如何取代循環語句”問題上思考了很久,循環語句不同於上面幾種,循環問題是最複雜的,光語句語法就有for和while等好幾種,如何取代這些傻吊語句成了一個問題。下面我來一一討論一下,表達式是否能夠完美的替換循環語句。

數組問題

Array對象(數組或者叫列表)是JavaScript裏最重要的一個類,也是原型鏈上方法最多的一個。事實上JS裏一切對象都是(散)列表。首先,所有循環都要使用數組,因爲數組的長度(n)是衡量循環的時間複雜度的標準,通常循環一遍的複雜度就是O(n)。

  • 循環遍歷

我們最常見的循環就是遍歷一個數組,那直接可以利用數組的forEach方法來遍歷:

// 遍歷數組語句for(let i=0; i<list.length; i++){}
// 遍歷數組方法list.forEach(item=>{})
  • 指定循環次數

for循環語句中經常出現需要指定循環的次數而沒有數組,我們可以通過構造一個定長數組來遍歷:

// 指定次數循環語句for(let i=0; i<n; i++){}
// 指定次數循環表達式Array(n).fill(true).forEach(()=>{})
  • continue中斷本次迭代

continue關鍵詞的作用是提前結束本次迭代進程,趕緊進入下一次迭代。在函數式數組的遍歷中只要使用return結束當前回調的執行就行啦。

// continue語句while (expression) {   if (condition) {      continue;   }}
// 用return結束當前迭代函數list.forEach(()=>{ if (condition) { return; }})
  • break結束循環

和continue不同,break關鍵詞會結束整個循環,forEach傳的回調函數永遠會執行列表的長度遍,所以forEach沒用,同理map和filter等一系列數組遍歷方法都不能用。可喜的是,數組有一些“可中斷的遍歷方法”,比如find方法本意是尋找一個數組元素,找到後就可以中斷遍歷;比如some方法本意是是否有“一些”元素符合回調條件,遍歷時一旦匹配到一個就會停止向下匹配;比如every方法本意是是否“所有”元素都符合回調條件,遍歷時只要發現1個元素不符合就會停止向下匹配。所以函數式編程中有3個數組方法可以實現循環的break。

// 傳統break語句for(let item of list){  if(condition)break;}
// 函數式break// findlist.find(item=>{  if(condition)return true;})// somelist.some(item=>{  if(condition)return true;})// everylist.some(item=>{  if(condition)return false;})
  • 無限循環

取代無限循環語句只要遞歸調用自己就好啦~

// 無限循環語句while(true){}
// 無限循環表達式(function loop(){ loop();})();
  • 異步循環(劃重點)

異步循環是最難的模擬的一個。假如我們有一個異步任務列表asyncTasks,想要串行執行而不是並行執行,也就是一個接着一個運行,如果想要並行執行任務非常簡單,只要Promise.all(asyncTasks)就行了,但能不能實現一個Promise.sequential呢?如果任務數量確定可以直接.then().then()...來鏈式調用,但如果數量是動態的就得用循環了。首先模擬一個tasks列表,其中每個元素都是async函數,即返回promise的函數:

tasks = [2000, 1000, 3000].map(time => async () => {    await new Promise(res => setTimeout(res, time));    console.log(time);})

使用循環語句來順序執行非常舒適,但如果你嘗試使用forEach來遍歷就會出現問題:

// 異步鏈用循環語句+await非常合適for(task of tasks){  await task();}
// 但是這樣你會發現,若干個異步任務併發執行了!tasks.forEach(async (task)=>{  await task();})

使用forEach,回調函數雖然是異步的,但是這個回調函數在一瞬間被併發執行了n次,每一次之間沒有等待,導致串行失敗。追根揭底,forEach無法順序執行異步任務的原因是,回調函數每次執行完全獨立,沒有關聯。貫穿Array原型鏈上幾十種遍歷方法中,似乎只有reduce和sort等寥寥幾個方法可以實現前後關聯。我們來模擬一個吧,利用reduce來polyfill一個Promise.sequential方法。

Promise.concurrent = Promise.all;Promise.sequential = tasks => tasks.reduce(async (chain, nextTask) => {    await chain;    return nextTask();}, Promise.resolve());
Promise.sequential(tasks)  .then(()=>console.log('finished'));// 依次打印2000,1000,3000,'finished'

老衲的解釋:這裏利用reduce將一系列promise串了起來,合成了一個大的promise,本質上仍然是通過.then將一個個promise鏈起來。注意,在async函數中即使return了一個promise.resolve(123),函數返回值將是另一個promise,只是解析值都是123。

經過本文的分析,所有的JavaScript語句,無論是聲明,條件,枚舉,循環還是流程控制語句,統統可以用函數表達式來替換,讓JS成爲第一個只由表達式組成的通用編程語言。如果認爲我有遺漏的地方或者說還有哪些語句是不可取代的,歡迎在底下留言評論。

參考

  • https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/

  • https://stackoverflow.com/questions/24586110/resolve-promises-one-after-another-i-e-in-sequence

  • https://jakearchibald.com/2017/await-vs-return-vs-return-await/

  • https://jimmy.blog.csdn.net/article/details/91038735

(完)

【日記】

看看本文的參考鏈接,可以發現外網站點都習慣於將文章的標題放在url上作爲文章ID,這種習慣的好處就是可以從url上直接讀出內容的主題,而我們的站點url很多都是一個個文章編號。不得不說,這些專業論壇的文章的不僅質量高,url的設計也很有語義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章