PHP 8.5 将于 2025 年 11 月发布,此次更新带来了一个期待已久的特性——管道运算符(
|>
)。这个看似简单的小特性,却有着巨大的潜力,不过它的实现过程却耗费了数年时间。什么是管道运算符?
管道运算符写作
|>
,看似简单。它会获取左侧的值,并将其作为唯一参数传递给右侧的函数(在 PHP 中为callable
),例如:plain
其真正有趣之处在于,当它被重复使用或串联起来形成一条“管道”时。比如在一个实际项目中,原本的代码经过改写使用管道操作后,变得更加清晰易读:
plain
如果不使用管道运算符,实现相同功能的代码要么是嵌套丑陋的:
plain
要么需要为每一步创建临时变量,这会带来额外的思维负担,而且这样的链式操作无法在
match()
块等单表达式上下文中使用,而管道链则可以。熟悉 Unix/Linux 命令行的人可能会发现它与 shell 管道
|
相似,这并非偶然,它们的作用本质上是一样的:将左侧的输出作为右侧的输入。管道运算符的由来
|>
运算符在许多语言中都有出现,主要是在函数式编程语言中。F# 和 OCaml 拥有基本相同的运算符,Elixir 则有一个稍复杂的版本(我们曾考虑过,但目前最终决定不采用)。实际上,有许多 PHP 库也提供了类似的功能,只是步骤更为繁琐,包括作者自己的 Crell/fp。而 PHP 管道的故事始于 Hack/HHVM,它是 Facebook 开发的 PHP 分支。Hack 包含了许多当时 PHP 5 所不具备的特性,其中很多最终被纳入了后续的 PHP 版本中,管道运算符便是其中之一,不过它有其独特之处。
2016 年,长期的 PHP 贡献者、HHVM 项目前开源负责人 Sara Golemon 提议将 Hack 的管道 直接移植到 PHP 中。在该 RFC 中,管道的右侧不是
callable
,而是一个表达式,并使用一个特殊的$$
标记(亲切地称为T_BLING
)将左侧的结果注入其中。按照这种方式,上面的示例代码会是这样:plain
这种方式虽然强大,但也有一定的局限性。它非常不标准,与其他任何语言都不同,而且还意味着一种奇怪的、一次性的部分调用函数的语法,这种语法仅在与管道结合使用时才有效。
该 RFC 并未进行投票。此后几年没有太多进展,直到 2020/2021 年。当时作者刚写完一本关于 PHP 函数式编程的书,其中谈到了函数组合,于是决定尝试实现管道运算符。特别是,作者与一个团队合作,将 部分函数应用(PFA)作为一个独立的 RFC,与更 传统的管道 分开。其想法是,将一个多参数函数(如上面的
array_column()
)转换为|>
所需的单参数函数,这本身就是一个有用的特性,并且应该可以在其他地方使用。其语法与 Hack 版本有所不同,为了使其更灵活:some_function(?, 5, ?, 3, ...)
,这会将一个 5 个或更多参数的函数转换为一个 3 个参数的函数。遗憾的是,由于一些引擎复杂性问题,PFA 没有通过,这在很大程度上也削弱了第二个管道 RFC。不过,我们也从中获得了一个安慰奖:由 Nikita Popov 提出的 一等可调用对象(
array_values(...)
语法),本质上是部分函数应用的一个“初级”、简化版本。时间快进到 2025 年,作者再次尝试实现管道运算符。这一次,在 PHP 基金会开发团队的 Ilija Tovilo 和 Arnaud Le Blanc 的大力帮助下,作者成功实现了它。真可谓事不过三。
大于各部分之和
如前所述,管道运算符看似简单。其实现本身几乎微不足道,实际上它只是临时变量版本的语法糖。然而,最好的特性是那些可以与其他特性结合使用,或者以新颖的方式使用以发挥更大作用的特性。
我们已经看到,一个漫长的数组操作过程现在可以浓缩为一个单一的链式表达式。现在想象一下,在只允许单一表达式的地方使用它,例如
match()
:plain
此外,右侧也可以是一个返回
Closure
的函数调用。这意味着通过一些返回函数的函数:plain
这几乎与长期讨论的标量方法相同!只是管道更加灵活,因为你可以在右侧使用任何函数,而不仅仅是那些被语言设计者视为方法的函数。
在这一点上,管道非常接近“扩展函数”,这是 Kotlin 和 C# 的一个特性,允许编写看起来像对象上的方法但实际上是独立函数的函数。它们的写法略有不同(这里用
|
而不是-
),但已经实现了 75% 的功能,而且是免费的。再进一步思考,如果管道中的某些步骤可能返回
null
怎么办?我们可以通过一个单一的函数“提升”我们链中的元素,以与空安全方法相同的方式处理null
值。plain
没错,我们只是用一个管道和一个单行函数实现了一个 Maybe 单子。
现在,考虑一下流的情况:
plain
其潜力是巨大的。可以毫不夸张地说,管道运算符是近年来“性价比”最高的特性之一,与构造函数属性提升等优秀特性不相上下。而这一切都要归功于这一点点语法糖。
接下来会发生什么?
尽管管道是一个重要的里程碑,但我们的工作还没有完成。目前正在积极推进不止一个后续 RFC。
第一个是再次尝试 部分函数应用。这是一个更大的特性,但一等可调用对象已经带来了许多必要的基础结构,这简化了实现。由于管道现在提供了一个自然的用例以及容易优化的点,值得再次尝试。在撰写本文时,它是否会纳入 PHP 8.5、推迟到 8.6 还是再次被拒绝还不确定,但作者对此充满希望。特别感谢 PHP 基金会团队的 Arnaud Le Blanc 接手并更新该实现。
第二个是 函数组合运算符。管道会立即执行,而函数组合则通过将两个函数首尾相连来创建一个新函数。这意味着上面的流示例可以通过组合
map()
调用来进一步优化:plain
这个特性肯定不会纳入 PHP 8.5,但作者希望能将其纳入 8.6。请继续关注。
特别感谢 PHP 基金会团队的 Ilija Tovilo 和 Arnaud Le Blanc 对管道实现的帮助。如果你想帮助推动 PHP 的发展,可以考虑 成为赞助商。