核心要点
作为程序员,您可能希望编写优雅、易维护、可扩展、可预测的代码。函数式编程(FP)的原则可以极大地帮助实现这些目标。
函数式编程是一种范式或风格,它重视不变性、一等函数、引用透明性和纯函数。如果您不明白这些词语的含义,请不要担心!我们将在本文中分解所有这些术语。
函数式编程起源于λ演算,这是一个围绕函数抽象和泛化的数学系统。因此,许多函数式编程语言看起来非常具有数学性。不过好消息是:您不需要使用函数式编程语言就能将函数式编程原则应用到您的代码中。在这篇文章中,我们将使用JavaScript,它有很多特性使其适合函数式编程,而不会局限于该范式。
函数式编程的核心原则
既然我们已经讨论了什么是函数式编程,那么让我们来谈谈FP背后的核心原则。
我喜欢将函数视为机器——它们接受输入或参数,然后输出某些内容,即返回值。纯函数没有“副作用”或与函数输出无关的操作。一些潜在的副作用包括打印值或使用console.log
将其记录下来,或者操作函数外部的变量。
这是一个非纯函数的示例:
<code class="language-javascript">let number = 2; function squareNumber() { number = number * number; // 非纯操作:操作函数外部的变量 console.log(number); // 非纯操作:控制台记录值 return number; } squareNumber();</code>
下面的函数是纯函数。它接受输入并产生输出。
<code class="language-javascript">let number = 2; function squareNumber() { number = number * number; // 非纯操作:操作函数外部的变量 console.log(number); // 非纯操作:控制台记录值 return number; } squareNumber();</code>
纯函数独立于函数外部的状态运行,因此它们不应该依赖于全局状态或自身外部的变量。在第一个示例中,我们使用了在函数外部创建的number
变量,并在函数内部对其进行设置。这违反了该原则。如果您严重依赖不断变化的全局变量,您的代码将不可预测且难以追踪。将更难以找出错误发生的位置以及值发生变化的原因。相反,仅使用输入、输出和函数局部变量可以更容易地进行调试。
此外,函数应遵循引用透明性,这意味着给定某个输入,它们的输出将始终相同。在上例函数中,如果我将2传递给函数,它将始终返回4。API调用或生成随机数并非如此,这只是两个例子。给定相同的输入,可能会也可能不会返回输出。
<code class="language-javascript">// 纯函数 function squareNumber(number) { return number * number; } squareNumber(2);</code>
函数式编程还优先考虑不变性,即不直接修改数据。不变性带来可预测性——您知道数据的价值,并且它们不会改变。它使代码简单、可测试,并且能够在分布式和多线程系统上运行。
当我们处理数据结构时,不变性经常发挥作用。JavaScript中的许多数组方法直接修改数组。例如,.pop()
直接从数组末尾删除一个项目,而.splice()
允许您获取数组的一部分。相反,在函数式范式中,我们将复制数组,并在此过程中删除我们想要消除的元素。
<code class="language-javascript">// 不具有引用透明性 Math.random(); // 0.1406399143589343 Math.random(); // 0.26768924082159495</code>
<code class="language-javascript">// 我们直接修改 myArr const myArr = [1, 2, 3]; myArr.pop(); // [1, 2]</code>
在函数式编程中,我们的函数是一等函数,这意味着我们可以像使用任何其他值一样使用它们。我们可以创建函数数组,将它们作为参数传递给其他函数,并将它们存储在变量中。
<code class="language-javascript">// 我们复制数组而不包含最后一个元素,并将其存储到变量中 let myArr = [1, 2, 3]; let myNewArr = myArr.slice(0, 2); // [1, 2] console.log(myArr);</code>
高阶函数是执行以下两项操作之一的函数:它们要么将函数作为其一个或多个参数,要么返回函数。JavaScript中内置了许多第一类高阶函数——例如map
、reduce
和filter
,我们可以使用它们来与数组交互。
filter
用于从旧数组返回一个新数组,该新数组仅包含符合我们提供的条件的值。
<code class="language-javascript">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</code>
map
用于迭代数组中的项目,根据提供的逻辑修改每个项目。在下面的示例中,我们通过将一个将我们的值乘以2的函数传递给map
来使数组中的每个项目加倍。
<code class="language-javascript">const myArr = [1, 2, 3, 4, 5]; const evens = myArr.filter(x => x % 2 === 0); // [2, 4]</code>
reduce
允许我们根据输入的数组输出单个值——它通常用于对数组求和、展平数组或以某种方式分组值。
<code class="language-javascript">const myArr = [1, 2, 3, 4, 5]; const doubled = myArr.map(i => i * 2); // [2, 4, 6, 8, 10]</code>
您也可以自己实现这些功能!例如,您可以创建一个类似这样的filter
函数:
<code class="language-javascript">const myArr = [1, 2, 3, 4, 5]; const sum = myArr.reduce((i, runningSum) => i + runningSum); // 15</code>
第二类高阶函数(返回其他函数的函数)也是一种相对频繁的模式。例如:
<code class="language-javascript">let number = 2; function squareNumber() { number = number * number; // 非纯操作:操作函数外部的变量 console.log(number); // 非纯操作:控制台记录值 return number; } squareNumber();</code>
您可能也对柯里化感兴趣,可以阅读一下!
函数组合是将多个简单的函数组合起来以创建更复杂的函数。因此,您可以拥有一个averageArray
函数,它将平均函数与求和函数组合起来,该求和函数对数组的值求和。各个函数很小,可以重复用于其他目的,并且组合在一起可以执行更完整的工作。
<code class="language-javascript">// 纯函数 function squareNumber(number) { return number * number; } squareNumber(2);</code>
优势
函数式编程产生模块化代码。您拥有可以反复使用的少量函数。了解每个函数的具体功能意味着查明错误和编写测试应该很简单,尤其是在函数输出应该是可预测的情况下。
此外,如果您尝试使用多个核心,您可以将函数调用分布到这些核心上,因此它可以提高计算效率。
如何使用函数式编程?
您不需要完全转向函数式编程来整合所有这些想法。您甚至可以将许多想法很好地与面向对象编程结合使用,后者通常被认为是其对手。
例如,React结合了许多函数式原则,例如不可变状态,但多年来主要使用类语法。它也可以在几乎任何编程语言中实现——除非您真的想,否则您不需要编写Clojure或Haskell。
即使您不是纯粹主义者,函数式编程原则也能在您的代码中产生积极的结果。
关于函数式编程的常见问题
函数式编程基于一些关键原则。首先是不变性,这意味着一旦设置了变量,就不能更改它。这消除了副作用,并使代码更容易理解。第二个原则是纯函数,这意味着函数的输出仅由其输入决定,没有任何隐藏的输入或输出。第三个原则是头等函数,这意味着函数可以用作其他函数的输入或输出。这允许高阶函数,并使代码更简洁、更容易理解。
函数式编程和过程式编程之间的主要区别在于它们处理数据和状态的方式。在过程式编程中,程序状态存储在变量中,并且可以随着时间的推移而改变。在函数式编程中,状态不会改变,而是从现有状态创建新状态。这使得函数式编程更易于预测和调试,因为没有副作用需要担心。
函数式编程提供了许多好处。它可以使代码更易于阅读和理解,因为它避免了副作用和可变状态。它还可以使代码更可靠,因为它鼓励使用始终为相同输入产生相同输出的纯函数。此外,函数式编程可以使代码更容易测试和调试,因为函数可以在隔离状态下进行测试。
虽然函数式编程有很多好处,但它也有一些挑战。它可能难以学习,特别是对于习惯于过程式或面向对象编程的人来说。用函数式风格实现某些算法也可能更困难。此外,函数式编程有时会导致效率较低的代码,因为它通常涉及创建新对象而不是修改现有对象。
许多编程语言在某种程度上支持函数式编程。有些语言,如Haskell和Erlang,是纯函数式的,而另一些语言,如JavaScript和Python,是支持函数式编程以及其他范式的多范式语言。即使是传统上与函数式编程无关的语言,如Java和C ,近年来也添加了支持函数式编程的功能。
在函数式编程中,尽可能避免副作用。这是通过使用不更改任何状态或执行任何I/O操作的纯函数来实现的。当需要副作用时,它们会被隔离和控制。例如,在Haskell中,副作用使用monad来处理,monad封装了副作用,并提供了一种以受控方式将它们链接在一起的方法。
高阶函数是一个函数,它将一个或多个函数作为参数,返回一个函数作为其结果,或者同时执行这两项操作。高阶函数是函数式编程的一个关键特性,因为它允许将函数用作数据。这可以导致更简洁和更具表现力的代码。
递归是一种技术,其中函数在其自身定义中调用自身。在函数式编程中,递归通常用作循环的替代品,因为循环涉及可变状态,而这在函数式编程中是避免的。递归可以用来解决各种各样的问题,从计算阶乘到遍历树。
柯里化是函数式编程中的一种技术,其中一个具有多个参数的函数被转换为一系列函数,每个函数只有一个参数。这允许函数的部分应用,其中一个函数应用于其某些参数,并返回一个采用其余参数的新函数。
函数式反应式编程 (FRP) 是一种编程范式,它结合了函数式编程和反应式编程。在 FRP 中,程序状态被建模为一系列随时间变化的不可变值,并且使用函数来转换和组合这些值。这使得更容易推理异步和事件驱动的程序,因为它避免了可变状态和副作用。
以上是什么是功能编程?的详细内容。更多信息请关注PHP中文网其他相关文章!