核心要點
作為程序員,您可能希望編寫優雅、易維護、可擴展、可預測的代碼。函數式編程(FP)的原則可以極大地幫助實現這些目標。
函數式編程是一種範式或風格,它重視不變性、一等函數、引用透明性和純函數。如果您不明白這些詞語的含義,請不要擔心!我們將在本文中分解所有這些術語。
函數式編程起源於λ演算,這是一個圍繞函數抽象和泛化的數學系統。因此,許多函數式編程語言看起來非常具有數學性。不過好消息是:您不需要使用函數式編程語言就能將函數式編程原則應用到您的代碼中。在這篇文章中,我們將使用JavaScript,它有很多特性使其適合函數式編程,而不會局限於該範式。
函數式編程的核心原則
既然我們已經討論了什麼是函數式編程,那麼讓我們來談談FP背後的核心原則。
我喜歡將函數視為機器——它們接受輸入或參數,然後輸出某些內容,即返回值。純函數沒有“副作用”或與函數輸出無關的操作。一些潛在的副作用包括打印值或使用console.log
將其記錄下來,或者操作函數外部的變量。
這是一個非純函數的示例:
let number = 2; function squareNumber() { number = number * number; // 非纯操作:操作函数外部的变量 console.log(number); // 非纯操作:控制台记录值 return number; } squareNumber();
下面的函數是純函數。它接受輸入並產生輸出。
let number = 2; function squareNumber() { number = number * number; // 非纯操作:操作函数外部的变量 console.log(number); // 非纯操作:控制台记录值 return number; } squareNumber();
純函數獨立於函數外部的狀態運行,因此它們不應該依賴於全局狀態或自身外部的變量。在第一個示例中,我們使用了在函數外部創建的number
變量,並在函數內部對其進行設置。這違反了該原則。如果您嚴重依賴不斷變化的全局變量,您的代碼將不可預測且難以追踪。將更難以找出錯誤發生的位置以及值發生變化的原因。相反,僅使用輸入、輸出和函數局部變量可以更容易地進行調試。
此外,函數應遵循引用透明性,這意味著給定某個輸入,它們的輸出將始終相同。在上例函數中,如果我將2傳遞給函數,它將始終返回4。 API調用或生成隨機數並非如此,這只是兩個例子。給定相同的輸入,可能會也可能不會返回輸出。
// 纯函数 function squareNumber(number) { return number * number; } squareNumber(2);
函數式編程還優先考慮不變性,即不直接修改數據。不變性帶來可預測性——您知道數據的價值,並且它們不會改變。它使代碼簡單、可測試,並且能夠在分佈式和多線程系統上運行。
當我們處理數據結構時,不變性經常發揮作用。 JavaScript中的許多數組方法直接修改數組。例如,.pop()
直接從數組末尾刪除一個項目,而.splice()
允許您獲取數組的一部分。相反,在函數式範式中,我們將復制數組,並在此過程中刪除我們想要消除的元素。
// 不具有引用透明性 Math.random(); // 0.1406399143589343 Math.random(); // 0.26768924082159495
// 我们直接修改 myArr const myArr = [1, 2, 3]; myArr.pop(); // [1, 2]
在函數式編程中,我們的函數是一等函數,這意味著我們可以像使用任何其他值一樣使用它們。我們可以創建函數數組,將它們作為參數傳遞給其他函數,並將它們存儲在變量中。
// 我们复制数组而不包含最后一个元素,并将其存储到变量中 let myArr = [1, 2, 3]; let myNewArr = myArr.slice(0, 2); // [1, 2] console.log(myArr);
高階函數是執行以下兩項操作之一的函數:它們要么將函數作為其一個或多個參數,要么返回函數。 JavaScript中內置了許多第一類高階函數——例如map
、reduce
和filter
,我們可以使用它們來與數組交互。
filter
用於從舊數組返回一個新數組,該新數組僅包含符合我們提供的條件的值。
let myFunctionArr = [() => 1 + 2, () => console.log("hi"), x => 3 * x]; myFunctionArr[2](2); // 6 const myFunction = anotherFunction => anotherFunction(20); const secondFunction = x => x * 10; myFunction(secondFunction); // 200
map
用於迭代數組中的項目,根據提供的邏輯修改每個項目。在下面的示例中,我們通過將一個將我們的值乘以2的函數傳遞給map
來使數組中的每個項目加倍。
const myArr = [1, 2, 3, 4, 5]; const evens = myArr.filter(x => x % 2 === 0); // [2, 4]
reduce
允許我們根據輸入的數組輸出單個值——它通常用於對數組求和、展平數組或以某種方式分組值。
const myArr = [1, 2, 3, 4, 5]; const doubled = myArr.map(i => i * 2); // [2, 4, 6, 8, 10]
您也可以自己實現這些功能!例如,您可以創建一個類似這樣的filter
函數:
const myArr = [1, 2, 3, 4, 5]; const sum = myArr.reduce((i, runningSum) => i + runningSum); // 15
第二類高階函數(返回其他函數的函數)也是一種相對頻繁的模式。例如:
let number = 2; function squareNumber() { number = number * number; // 非纯操作:操作函数外部的变量 console.log(number); // 非纯操作:控制台记录值 return number; } squareNumber();
您可能也對柯里化感興趣,可以閱讀一下!
函數組合是將多個簡單的函數組合起來以創建更複雜的函數。因此,您可以擁有一個averageArray
函數,它將平均函數與求和函數組合起來,該求和函數對數組的值求和。各個函數很小,可以重複用於其他目的,並且組合在一起可以執行更完整的工作。
// 纯函数 function squareNumber(number) { return number * number; } squareNumber(2);
優勢
函數式編程產生模塊化代碼。您擁有可以反複使用的少量函數。了解每個函數的具體功能意味著查明錯誤和編寫測試應該很簡單,尤其是在函數輸出應該是可預測的情況下。
此外,如果您嘗試使用多個核心,您可以將函數調用分佈到這些核心上,因此它可以提高計算效率。
如何使用函數式編程?
您不需要完全轉向函數式編程來整合所有這些想法。您甚至可以將許多想法很好地與面向對象編程結合使用,後者通常被認為是其對手。
例如,React結合了許多函數式原則,例如不可變狀態,但多年來主要使用類語法。它也可以在幾乎任何編程語言中實現——除非您真的想,否則您不需要編寫Clojure或Haskell。
即使您不是純粹主義者,函數式編程原則也能在您的代碼中產生積極的結果。
關於函數式編程的常見問題
函數式編程基於一些關鍵原則。首先是不變性,這意味著一旦設置了變量,就不能更改它。這消除了副作用,並使代碼更容易理解。第二個原則是純函數,這意味著函數的輸出僅由其輸入決定,沒有任何隱藏的輸入或輸出。第三個原則是頭等函數,這意味著函數可以用作其他函數的輸入或輸出。這允許高階函數,並使代碼更簡潔、更容易理解。
函數式編程和過程式編程之間的主要區別在於它們處理數據和狀態的方式。在過程式編程中,程序狀態存儲在變量中,並且可以隨著時間的推移而改變。在函數式編程中,狀態不會改變,而是從現有狀態創建新狀態。這使得函數式編程更易於預測和調試,因為沒有副作用需要擔心。
函數式編程提供了許多好處。它可以使代碼更易於閱讀和理解,因為它避免了副作用和可變狀態。它還可以使代碼更可靠,因為它鼓勵使用始終為相同輸入產生相同輸出的純函數。此外,函數式編程可以使代碼更容易測試和調試,因為函數可以在隔離狀態下進行測試。
雖然函數式編程有很多好處,但它也有一些挑戰。它可能難以學習,特別是對於習慣於過程式或面向對象編程的人來說。用函數式風格實現某些算法也可能更困難。此外,函數式編程有時會導致效率較低的代碼,因為它通常涉及創建新對象而不是修改現有對象。
許多編程語言在某種程度上支持函數式編程。有些語言,如Haskell和Erlang,是純函數式的,而另一些語言,如JavaScript和Python,是支持函數式編程以及其他範式的多範式語言。即使是傳統上與函數式編程無關的語言,如Java和C ,近年來也添加了支持函數式編程的功能。
在函數式編程中,盡可能避免副作用。這是通過使用不更改任何狀態或執行任何I/O操作的純函數來實現的。當需要副作用時,它們會被隔離和控制。例如,在Haskell中,副作用使用monad來處理,monad封裝了副作用,並提供了一種以受控方式將它們鏈接在一起的方法。
高階函數是一個函數,它將一個或多個函數作為參數,返回一個函數作為其結果,或者同時執行這兩項操作。高階函數是函數式編程的一個關鍵特性,因為它允許將函數用作數據。這可以導致更簡潔和更具表現力的代碼。
遞歸是一種技術,其中函數在其自身定義中調用自身。在函數式編程中,遞歸通常用作循環的替代品,因為循環涉及可變狀態,而這在函數式編程中是避免的。遞歸可以用來解決各種各樣的問題,從計算階乘到遍歷樹。
柯里化是函數式編程中的一種技術,其中一個具有多個參數的函數被轉換為一系列函數,每個函數只有一個參數。這允許函數的部分應用,其中一個函數應用於其某些參數,並返回一個採用其餘參數的新函數。
函數式反應式編程 (FRP) 是一種編程範式,它結合了函數式編程和反應式編程。在 FRP 中,程序狀態被建模為一系列隨時間變化的不可變值,並且使用函數來轉換和組合這些值。這使得更容易推理異步和事件驅動的程序,因為它避免了可變狀態和副作用。
以上是什麼是功能編程?的詳細內容。更多資訊請關注PHP中文網其他相關文章!