天天看點

利用php數組函數進行函數式程式設計

因為一個BUG, 我在一個搖搖欲墜,幾乎碰一下就會散架的項目中某一個角落中發現下面這樣一段代碼

利用php數組函數進行函數式程式設計

這段程式與那個BUG有密切的關系。 我來回反複的捉摸這段代碼, 發現這段代碼實作了兩個功能

第一個是在一個從資料庫中讀取的清單數組中找出某個值是最大的一條記錄, 并且把這個最大的值和跟這個值相關的時間給取出來。

第二個比較複雜 ,是将這個清單數組中的值映射到另外一個清單數組中, 可以把這個過程看作是SQL中的JOIN操作, 隻是JOIN的條件異常複雜 ,在這裡我也不詳述了,閱讀的同學也不必去深入探究。

就這段代碼來說, 很難通過大緻觀察就了解代碼的意思 , 代碼之中光循環就套了3層, 而且還有多處複雜的條件判斷,代碼格式混亂,連編碼的底線縮進都沒有滿足。 可悲的是這種類型的代碼廣泛存在于全球範圍内無數Web伺服器之上, 每天運作着。

在很久以前, 那會我還很年輕, 看到項目中哪個地方代碼有問題,我就難受, 必須改掉它。 後來我發現, 爛代碼就像地溝油, 在我所生活的城市, 到哪裡都能碰的到, 除非不吃飯, 否則就隻能睜一隻眼閉一隻眼,隻要不是味道有問題, 吃也就吃了。

然而,這次卻不一樣, 這段代碼運作在某個功能項的關鍵部位, 不透徹的了解清晰這段代碼, 以後出現問題還是會被卡在這裡。雖然現在我了解了這段代碼的意思 ,但過些天回過頭來, 我又會忘掉這段代碼所表達的意義。這并不是我的記憶力問題的, 而是因為這段代碼所表達的意途不夠清晰。

于是我把代碼重構成了下面這個樣子, 代碼本身的功能并沒有變化

利用php數組函數進行函數式程式設計

是不是還是看不明白代碼所表達的意思? 沒關系, 因為這段代碼所表示的功能太過于複雜 ,而且還依賴于代碼所有的整個函數的上下文, 是以無法了解也無可厚非。 但是從代碼結構上來看, 重構後的代碼的卻清晰了不少。

我将原本擁擠在一起的兩個功能進行了拆分, 上面部份是求最大值, 下面部份是對兩個數組進行映射。 這裡我用到了兩個PHP中數組的函數 array_map和array_reduce, 這篇文章想表達的主線思路就是利用此類函數來提高PHP代碼的可讀性。 這類函數主要包括以下4個函數

array_filter

array_map

array_walk

array_reduce

這4個函數威力巨大, 在處理清單數組方面可以完全替換掉for、foreach、while這些循環控制語句, 這也是函數式程式設計方式在PHP的一部份展現。

1.array_filter函數

利用php數組函數進行函數式程式設計

這段代碼比較好了解,将數組中性别字段為女的資料項提取出來。 整段代碼的邏輯大緻如下

1.定義result數組, 用來存放結果

2.循環數組, 對每一個資料項進行條件判斷, 檢視其中的性别字段是否為女

3.如符合條件則放入result數組中

這是原汁原味的指令式程式代碼。

如果data變量中的資料并非存放于php數組中, 而是存在于關系數庫的表之中, 那何取得性别為女的資料結果呢? 對于程式員來說這貌似是一個更加簡單的問題,一句SQL語句就搞定了

利用php數組函數進行函數式程式設計

顯然, 利用SQL查詢資料更加友善,意途也更加清晰,畢間一個SQL表達 式就将所有的程式邏輯都給表達了現來。這句SQL隻表達了:“我需要性别為女的資料,至于怎麼拿, 我不管 ”, 除了結果 , 其它的它一概不知。

我們不妨把這種思路引入到PHP程式設計之中,不也意味着我們的PHP程式的邏輯表達也更加清晰,代碼的可讀性也更高的。所幸, 這種利用表達式程式設計的方法在PHP中也完全可以實作。

利用php數組函數進行函數式程式設計

利用array_filter函數,可以輕松的完成這個任務, 仔細觀察一下, 是不是原來的程式邏輯都不見了,包括定義數組、循環、條件判斷這些都不見了,邏輯方面是隻剩下了一個性别比較語句,這對于代碼所實作的功能一目了然。 和上面的SQL比較一下, 這裡的性别判斷語句就是SQL中where子句後面的條件判斷, 而array_filter函數其實就是SQL中的where子句。 這就是SQL語句面向結果程式設計的邏輯原封不變的在PHP中的展現,也就是時下最流行的“聲明性程式設計”或者也稱為“表達式程式設計”。

此外, 代碼中性别判斷語句所在的位置稱之為lambda表達式, 更通俗一些的叫法是匿名函數。不難看出, 在SQL的where條件中編寫條件判斷遠不如在匿名函數中寫PHP代碼來的靈活,在where條件中隻能執行or和and邏輯,而在php匿名函數中可以随便怎麼寫,隻要函數的傳回值是個布爾值就可以了,這也是php聲明性程式設計優于SQL聲明性程式設計的地方。

2.array_map函數 

再來看一個例子

利用php數組函數進行函數式程式設計

資料中的性别字段是中文的,值也是中文的, 現在想把字段名和字段值都改為英文的, 就可以用上面這段代碼實作, 至于實作的邏輯這裡不贅述了。

下面是利用SQL的實作方式

利用php數組函數進行函數式程式設計

SQL中case when語句好像不太好看, 但是不影響整體邏輯的表達。 将這段SQL轉換成PHP的方式實作

利用php數組函數進行函數式程式設計

相比之前的PHP實作, 是不是簡潔明了了許多。

在這裡使用到了 array_map函數 。 在SQL語句中以select語句最為常用, select的字面意思是“選擇”,而select語句也被稱之為選擇查詢, 事實上從關系資料庫的角度來說,select被稱之為“投影”, 并不是查詢什麼的。 換言之, select 語句隻是将SQL的查詢結果以一定的方式(選字段、計算值等等)提取出來了。 php中的array_map表達的也是這層意思, “映射”與“投影”完全是一種意思的不同表達。

3.array_walk函數

array_walk函數沒有像 array_map和array_filter這樣深刻的意義, 但是它在設計可讀性良好的代碼時也是不可或缺的。

array_walk是for或foreach語句的替代函數

利用php數組函數進行函數式程式設計

以上代碼分别是 foreach和array_walk對于周遊數組的實作方式。 看起來, 好像array_walk的實作方式更加複雜, 但是在更深層次的語義方面

foreach表達的是循環周遊, 但是在這個循環的過程中,要做什麼樣的處理,是沒有任何限制的, 删除被周遊的數組的某一項 ,或者修改一個十萬八千裡以外的變量的值,這便是所謂的“代碼副作用”,俗話說“白蟻雖小, 危害無窮”, 當這些看似微不足道的副作用發展壯大時, 便會給程式員維護程式代碼帶來的障礙是緻命的。

而array_walk函數預設情況下所有執行代碼的作用域都在匿名函數内,如果要依賴或操作函數之外的資料, 必須通過匿名函數的use關鍵字導入。通俗一點的請, array_walk函數的權限不如foreach來的大, 是以,使用array_walk函數後,雖然無法讓你随心所欲的程式設計,但是大限度的減少了你代碼的副作用,兩相權衡array_walk所帶來的好處還是有值得使用它的理由的。 首先, 大多數時候寫代碼根本不需要太大的“權限”,其次, 把代碼所影響的範圍控制到最小好處不言而喻。微信張小龍講過,微信做的最好的一點便是“克制”,我們寫代碼又何嘗不是。這一點array_filter和array_map中也有展現, 寬泛的講,所有使用匿名函數的地方都能享受到這個好處。

array_walk所表達的語義就是“假如你需要用到我, 那麼你除了周遊以外,其它的事情最好都别幹,否則你還是去用原生的foreach吧”

4.array_reduce函數

array_reduce是上面所講的三個函數的集大成者,這三個函數的底層完全可以由array_reduce實作。

先看一下下面的php代碼

利用php數組函數進行函數式程式設計

正常的PHP寫法,代碼分别用于計算數組記錄中平均年齡和最大年齡,代碼需要循環數組,并把計算結果存入一個标量(單個值,區分于清單變量)。

假如要以表達式程式設計的方式完成編寫這兩個功能, 利用array_filter、 array_walk、array_map三個函數是很難一部到位的實作的。

于是, 就到了array_reduce大顯身手的時候了

利用php數組函數進行函數式程式設計

上面的代碼是求平均年齡和最大年齡的表達式程式設計的實作,如果對array_reduce函數的工作機制不了解,看上面兩段代碼會覺得在看天書。

利用php數組函數進行函數式程式設計

這是 array_reduce函數的實作代碼,函數有3個參數, 3個參數的作用分别是

第一個參數$data, 就要是處理的資料源

第二個參數$callback,循環周遊時會被調用的函數,函數傳回的結果在下一次循環調用時會被再次當成參數傳入。

第三個參數$initial,作為$callback函數被初次調用時的參數傳遞

再來一個遞歸版本的array_reduce實作,幫助更好的了解這個函數的使用意義

利用php數組函數進行函數式程式設計

善用array_reduce函數幾乎可以替換掉絕大多數需要使用foreach、for、while語句的代碼。

在标準的函數式程式設計語言中, 是沒有循環控制語句的,假如要進循環計算, 都是使用此類函數來實作的, 如果某些極端的情況下這些函數無法滿足需求,那麼就以手動寫遞歸來實作循環, 以達到表達式程式設計的目的。

總結一下, 為什麼要在寫php代碼時使用這4個函數

1.通過函數本身的意義就能表達出代碼實作了什麼樣的功能,而不用去琢磨代碼具體細節來了解代碼的作用

2.表達式程式設計相對于指令式程式設計能極大的簡化功能的實作過程, 提升編碼效率

3.表達式程式設計對于代碼的可讀性、可維護性具有非凡的意義

4.利用匿名函數控制代碼的副作用

5.由傳統的面向過程式程式設計向現代化的函數式程式設計靠攏

補充:

通過前面示例的講解, 利用這4個函數實作的代碼相對于傳統的實作方式并沒有不可思議的變化, 然而, 當需要解決的問題複雜到一定程度時, 合理利用這4個函數會使代碼的複雜性大規模下降。

知乎:https://www.zhihu.com/people/aspwebchh

github:https://github.com/aspwebchh

email: [email protected]