許多程序員喜歡談論函數式編程,但如果你問他們是否實際運用過,大多數的回答都會是“沒有”。原因很簡單:我們初學編程時,就被教導以命令式思維方式思考,即程序流程圖和步驟。因此,本文將解釋函數式編程的一些重要概念以及如何在 PHP 中編寫函數式代碼。
關鍵要點
函數式編程的重要概念
維基百科將函數式編程定義為“一種將計算視為數學函數的求值並避免狀態和可變數據的編程範例”。在函數式編程中,函數被視為一等公民,而在命令式編程中,我們主要關注數據以及改變數據以達到預期結果的步驟。當我們說函數是一等公民時,這意味著我們可以像在命令式編程中使用值一樣使用函數。它們可以作為參數傳遞給函數,在另一個函數內定義,甚至可以作為結果返回。換句話說,“函數就是值”。我們稍後將再次討論這一點,但函數式編程還有許多其他重要概念。僅舉幾例:
不變性是指變量的值一旦定義就不能更改的行為。不同的語言有不同的實現方式;例如,在 PHP 中,使變量不變的唯一方法是將其定義為常量。
遞歸在函數式編程中也很突出。在命令式編程中,當我們需要操作集合或數組時,可以使用 for 和 foreach 等循環結構,遍歷每個元素並使用臨時變量來保存當前值。但是,由於不變性,這種方法在函數式編程中是不可能的。遞歸是答案,因為這種簿記是通過調用堆棧隱式完成的。假設我們想編寫一個函數來查找數組中所有元素的總和(暫時忘記 array_sum() 的存在)。以函數式風格,我們將編寫:
<code class="language-php"><?php function sum($array) { if (empty($array)) return 0; else return $array[0] + sum(array_slice($array, 1)); } $total = sum(array(1, 2, 3)); // 6 ?></code>
空列表將返回 0,這是我們的基本條件。對於包含多個值的數組,它將返回第一個元素與所有其他元素的遞歸總和的相加結果。
如果一個函數不改變自身外部對象的(例如全局變量或靜態變量)的值,並且沒有任何 I/O 效應(例如寫入文件、數據庫等),則稱該函數沒有副作用。此類函數也稱為純函數。對於給定的參數集,純函數的輸出將始終相同,這導致了另一個稱為引用透明性的屬性。當函數是引用透明的時,我們可以用它的值替換該函數,而不會影響程序的行為。所有數學函數都是純函數,而日期函數、rand() 等則是非純函數。
上述概念幾乎可以在任何編程語言中實現,但一等公民函數和高階函數是函數式編程的兩個最顯著特徵。我已經解釋了一等公民函數意味著函數可以被視為值。高階函數是可以將函數作為參數並可以返回函數作為結果的函數。最近添加的兩個重要功能使我們能夠在 PHP 中編寫高階函數:lambda 表達式和閉包。
lambda 函數(也稱為匿名函數)只是一個沒有名稱的函數。當我們定義匿名函數時,會返回對該函數的引用,該引用存儲在一個變量中以供以後使用。我們使用此變量在需要時調用該函數。許多不同的語言都採用了這個概念。事實上,你可能在日常的 JavaScript 編程中使用 lambda 函數,將它們作為不同用戶交互和 Ajax 調用的回調函數。
<code class="language-javascript">$("#myButton").click(function () { // do something });</code>
這段代碼非常簡單易懂,這可能會讓我們忘記它的函數式方面。 PHP 在 5.3 版本中引入了這個強大的功能,它允許我們以類似的方式編寫 PHP 代碼:
<code class="language-php"><?php $square = function ($arg) { return $arg * $arg; }; $value = $square(2); // 4 ?></code>
在談論函數,特別是匿名函數時,了解如何處理變量作用域非常重要。例如,JavaScript 允許你在 lambda 內部訪問外部作用域的變量,而 PHP 則不允許。 lambda 內部有它自己的作用域,就像常規 PHP 函數一樣。
有時,你可能希望在函數內部引用父作用域中的變量。閉包類似於 lambda 函數,但略有不同,你可以訪問外部作用域的變量。我們可以使用“reach out”並使用 PHP 的 use 關鍵字綁定外部變量,該關鍵字也在 PHP 5.3 中引入。
<code class="language-php"><?php function sum($array) { if (empty($array)) return 0; else return $array[0] + sum(array_slice($array, 1)); } $total = sum(array(1, 2, 3)); // 6 ?></code>
在這種情況下,我們不會在每次調用函數時都傳遞利率。相反,我們將其定義在外部,並使用 use 關鍵字使其在函數內部可用。
簡單來說,部分函數是從現有函數創建的函數,通過部分應用其參數。你只需要在調用創建的函數時傳遞剩餘的參數。我們可以使用閉包在 PHP 中創建部分函數。這是一個示例,用於根據其長度、寬度和高度查找盒子的體積。所有參數都是可選的;如果你沒有提供所有參數,該函數將返回另一個函數以接受剩餘的必要值。
<code class="language-javascript">$("#myButton").click(function () { // do something });</code>
所有參數都是可選的。首先檢查調用者是否傳遞了所有參數。在這種情況下,我們可以通過將長度、寬度和高度相乘直接返回體積。如果參數數量少於參數,則返回一個新函數以查找預先設置了給定參數的體積。現在假設我們大多數時候都在查找長度固定的盒子(例如 10)的體積。這可以通過將 10 作為第一個參數輕鬆完成,或者我們可以通過將 10 作為第一個參數來創建部分函數,然後隻請求剩餘的值。
<code class="language-php"><?php $square = function ($arg) { return $arg * $arg; }; $value = $square(2); // 4 ?></code>
柯里化是部分函數的一種特殊情況,你將一個接受多個參數的函數轉換為多個每個都接受單個參數的函數。例如,類似於 f(x,y,z) 到 f(x)(y)(z)(儘管 PHP 語法不允許像這樣嵌套函數調用)。如果你有興趣了解更多信息,Timothy Boronczyk 撰寫了一篇關於使用實際示例進行柯里化的優秀文章。
優點和缺點
函數式編程的功能在 PHP 中有很多實際用途。例如,lambda 函數在使用回調函數時被廣泛使用。例如,使用 Slim 框架,你可以定義如下路由:
<code class="language-php"><?php function sum($array) { if (empty($array)) return 0; else return $array[0] + sum(array_slice($array, 1)); } $total = sum(array(1, 2, 3)); // 6 ?></code>
當請求 URL 與此路由匹配時,Slim 會調用回調函數。 Vance Lucas 之前寫過一些關於 Lambda 函數的其他有趣用例的文章。通過避免狀態和可變數據來鼓勵安全編程。在函數式編程中,你應該編寫每個只做一件事情並且不會產生任何副作用的函數。該範例對模塊化和函數簡潔性的強調可以使更容易根據不同的、小的子程序來推斷你的程序。函數式編程還可以幫助你編寫專注於你想要實現的目標的代碼,而不是明確地管理過程中的偶然事件(將遞歸與必須管理循環計數器變量進行比較)。但是請記住,傳統上與函數式編程相關的某些優點並不適用於 PHP,因為它並非設計為函數式編程語言。例如,無副作用的函數非常適合併行處理,但 PHP 腳本不會以這種方式運行。也不總是容易計算遞歸和惰性函數的成本,並且由於內部開銷,可能會出現嚴重的性能問題。有時,為了提高效率,用可變性來編寫程序更有意義。也許函數式編程最大的缺點是對於那些接受過命令式訓練的人來說,它的學習曲線非常陡峭。但總的來說,函數式編程很有趣,學習它將為你提供思考舊問題的新的工具,幫助你作為程序員成長。它不是一個萬能的解決方案,但可以根據需要應用於更簡潔、更優雅的 PHP 代碼。
總結
函數式編程不僅僅是一種編程範例;它是一種思考和推理程序的方式。如果你能進行函數式思考,你幾乎可以用任何語言進行函數式編程。在本文中,我們討論了函數式編程的基礎知識,利用 PHP 的功能來編寫並提供它們的示例。雖然本文中給出的示例可能對你來說並不實用,但你會發現許多情況,函數式風格可以顯著提高你正在編寫的代碼的質量。嘗試尋找這樣的案例,進行函數式思考,並享受樂趣! 圖片來自 Fotolia
關於 PHP 函數式編程的常見問題
函數式編程和麵向對象編程是 PHP 中使用的兩種不同的範例。主要區別在於它們如何管理狀態和數據。在函數式編程中,函數是一等公民,並且沒有狀態的概念。這意味著給定相同的輸入,函數將始終產生相同的輸出。另一方面,面向對象編程圍繞對象及其交互展開,這些對象可以維護狀態並隨時間變化。這可能會導致不同的輸出,即使輸入相同也是如此。
要開始使用 PHP 進行函數式編程,你需要了解基本概念,例如純函數、不變性和高階函數。然後,你可以開始編寫不改變狀態且不產生副作用的函數。 PHP 具有支持函數式編程的內置函數,例如 array_map
、array_filter
和 array_reduce
。你還可以使用 Laravel 集合之類的庫,這些庫為使用數據數組提供流暢、便捷的包裝器。
PHP 中的函數式編程可以編寫更簡潔、更易讀的代碼。它可以幫助你避免常見的編程問題,例如副作用和狀態更改,這可以使你的代碼更易於預測和測試。函數式編程還可以編寫更模塊化的代碼,因為函數可以輕鬆組合和重用。
雖然函數式編程有其優點,但它也有一些挑戰。 PHP 最初並非設計為函數式編程,因此某些功能可能不如為函數式編程設計的語言那樣強大或高效。此外,函數式編程需要不同的思維方式,對於習慣於命令式或面向對象編程的開發人員來說,學習曲線可能很陡峭。
是的,PHP 是一種多範例語言,這意味著你可以混合使用不同的編程風格。你可以將面向對象編程用於從狀態和行為中受益的應用程序部分,並將函數式編程用於從無狀態、無副作用函數中受益的應用程序部分。這可以讓你靈活地為應用程序的每個部分選擇最佳方法。
函數式編程對 PHP 性能的影響可能會有所不同。在某些情況下,函數式編程可以編寫更高效的代碼,因為它避免了狀態更改和副作用。但是,在其他情況下,它可能效率較低,因為它通常涉及創建新對象而不是修改現有對象。重要的是要分析和測試你的代碼,以確保它滿足你的性能要求。
有很多資源可用於學習 PHP 函數式編程。一些好的起點包括 PHP 手冊(其中有一節關於函數式編程的內容)以及在線教程和文章。還有一些關於這個主題的書籍,例如 Gilles Crettenand 的《PHP 函數式編程》。
是的,你可以在 PHP 中使用函數式編程進行 Web 開發。函數式編程可以幫助你編寫更簡潔、更模塊化的代碼,這在 Web 開發環境中可能很有益。但是,請記住,PHP 是一種多範例語言,因此你也可以使用其他編程風格,例如面向對象編程。
在函數式編程中,錯誤處理通常是通過使用單子來完成的,單子是一種可以表示計算而不是值的數據結構。在 PHP 中,你可以使用 Maybe 單子進行錯誤處理。這允許你將操作鏈接在一起,如果任何操作失敗,則跳過其餘鏈。
函數式編程可以用於大型應用程序,但這取決於應用程序的具體要求。函數式編程可以編寫更簡潔、更模塊化的代碼,這在大規模環境中可能很有益。但是,在某些情況下它也可能效率較低,因此重要的是要考慮權衡。
以上是PHP主| PHP中的功能編程的詳細內容。更多資訊請關注PHP中文網其他相關文章!