了解 PHP 函数式编程
任何面向对象的编程语言都是通过对象来表示一切,而函数式编程则不同,它开始用函数来表示一切。面向对象编程和函数式编程并不相互排斥。面向对象编程侧重于通过封装和继承实现代码的可维护性和可重用性,而函数式编程与面向状态的命令式编程不同,它侧重于面向值的编程,将计算视为纯数学评估,避免了可变性和状态修改。在使用 OOP 时,面临的挑战之一是我们创建的对象会带来许多额外的属性或方法,无论我们是否在特定情况下使用它。以下是函数式编程的三个主要特点:
-
不变性
-
纯函数和引用透明性
-
一等公民函数
-
高阶函数
-
函数组合(柯里化)
-
不变性的概念告诉我们,物体在创建后不会发生变化。它在整个生命周期内都不会改变。它有一个很大的优点,就是我们在使用对象时不需要重新验证它。此外,如果需要可变性,我们可以创建对象的副本或创建具有新属性的新对象。
到目前为止,我们在本书中看到了很多使用代码块、循环和条件来实现数据结构和算法的示例。一般来说,这就是所谓的命令式编程,它要求定义执行的每一步。例如,请看下面的代码块:
Unresolved include directive in modules/ROOT/pages/ch13/ch13-01.adoc - include::example$Chapter13/1.php[]
前面的代码实际上是将每个名称的第一个字符设置为大写。从逻辑上讲,这段代码是正确的,我们也在这里逐步介绍了这段代码,以便我们理解其中的含义。不过,使用函数式编程方法,这也可以写成一行代码,如下所示:
$languages = array_map('ucfirst', $languages);
这两种方法都做同样的事情,但其中一种方法的代码块比另一种小。后一种方法被称为声明式编程。命令式编程侧重于算法和步骤,而声明式编程侧重于函数的输入和输出以及递归(而非迭代)。
函数式编程的另一个重要方面是它没有任何副作用。这是一个重要的特性,它确保函数不会对输入的任何地方产生任何隐含影响。函数式编程的一个常见例子是在 PHP 中对数组进行排序。通常,参数是通过引用传递的,而当我们得到排序后的数组时,实际上会破坏初始数组。这就是函数副作用的一个例子。
在跳转到 PHP 函数式编程之前,让我们先来探讨一些函数式编程术语,我们将在下面的章节中接触到这些术语。
一等公民函数
具有一等公民函数的语言允许以下行为:
-
将函数赋值给变量
-
将其作为参数传递给另一个函数
-
返回函数
PHP 支持所有这些行为,因此 PHP 函数都是一等公民函数。在我们前面的例子中,ucfirst
函数就是一等公民函数的一个例子。
闭包
闭包与 lambda 函数非常相似,但基本区别在于闭包可以访问其外层作用域变量。在 PHP 中,我们不能直接访问外作用域变量。为此,PHP 引入了关键字 "use",用于将任何外层作用域变量传递给内部函数。
柯里化
柯里化是一种将包含多个参数的函数转换为函数链的技术,在函数链中,每个函数都只包含一个参数。换句话说,如果一个函数可以写成 f(x,y,z),那么其柯里化版本就是 f(x)(y)(z):
Unresolved include directive in modules/ROOT/pages/ch13/ch13-01.adoc - include::example$Chapter13/1.php[]
在这里,我们编写了一个带有三个参数的简单函数,当调用数字时,它将返回数字之和。现在,如果我们把这个函数写成柯里化,它将看起来像这样:
Unresolved include directive in modules/ROOT/pages/ch13/ch13-01.adoc - include::example$Chapter13/1.php[]
现在,如果我们将 currySum 作为柯里化函数运行,就会得到上例中的结果 60。这对于函数式编程来说是一个非常有用的功能。
以前,在 PHP 中不可能调用 f(a)(b)(c) 这样的函数。从 PHP 7.0 开始,统一变量语法允许立即执行可调用函数,就像我们在本例中看到的那样。然而,在 PHP 5.4 及更高版本中,要做到这一点,我们必须创建临时变量来存储 lambda 函数。 |
部分应用
部分应用或部分函数应用是一种减少函数参数数量或使用部分参数并创建另一个函数来处理剩余参数的技术,以便产生与同时调用所有参数时相同的输出结果。如果我们认为我们的求和函数是部分函数,即它需要三个参数,但我们可以用两个参数调用它,然后再添加剩余的一个参数。下面是代码示例。本例中使用的求和函数来自上一节:
Unresolved include directive in modules/ROOT/pages/ch13/ch13-01.adoc - include::example$Chapter13/1.php[]
有时,我们会混淆 柯里化(currying)和 部分应用(partial application),尽管它们的方法和原理完全不同。
正如我们所看到的,在处理 PHP 中的函数式编程时有很多事情需要考虑。在 PHP 中使用函数式编程从头开始实现数据结构将是一个漫长的过程。为了解决这个问题,我们将探索一个优秀的 PHP 函数编程库,名为 Tarsana。它是开源的,采用 MIT 许可。我们将探索这个库,并以此为基础在 PHP 中实现函数式数据结构。