PHP 8.6 将引入部分函数应用,管道操作符 RFC 推进,函数式编程更流畅

部分函数应用(PFA)是指仅用函数的部分必需参数调用函数,将其余参数延迟到稍后时间。它在逻辑上等同于以下内容:
function f(int $a, int $b, int $c) { }
$partialF = fn (int $b) => f(1, $b, 3);
然而,使用箭函数通常很繁琐,因为它需要手动复制所有参数信息。这对作者和读者来说都是不必要的重复工作。
这在处理回调或新的管道操作符时尤为相关。例如:
$result = array_map(
static fn (string $string): string => str_replace('hello', 'hi', $string),
$arr
);
$foo |> static fn (array $arr) => array_map(
static fn (string $string): string => str_replace('hello', 'hi', $string),
$arr
);
虽然上面的例子包含了“所有修饰”,包括技术上可选的语法,但通常建议包含类型信息,许多静态分析工具会要求这样做。即使没有可选部分,代码中仍有许多不必要且繁琐的间接方式。
使用这里提出的 PFA,上面的例子可以简化为:
$result = array_map(
str_replace('hello', 'hi', ?),
$arr
);
$foo |> array_map(
str_replace('hello', 'hi', ?),
?
);
这将产生相同的结果,但对所有相关人员来说要实用得多。
PFA 是许多函数式语言的核心特性。虽然在 PHP 中像 Haskell 那样深度集成 PFA 概念不可行,但 PFA 的实用性优势使其成为 PHP 不断增长的函数式功能的关键组成部分。
从一个角度来看,PFA 是 First-class callable syntax(FCC,PHP 8.1 引入)的自然扩展。相反,FCC 是 PFA 的退化情况。最初的 FCC RFC 被呈现为 PFA 提出的语法的“初级版本”。FCC 在过去几年中显然证明了其在代码简化方面的价值,而 PFA 使其更加强大。
此 RFC 与之前的 PFA RFC 大体相似,尽管有一些细节变化。实现还利用了 FCC 的现有工作。
请注意,就本 RFC 而言,“函数”指的是任何可调用对象。命名函数、命名方法、命名静态方法、匿名函数、短匿名函数、可调用对象等。部分应用适用于所有这些。
提案
概述
部分应用由一个函数调用组成,其中一个或多个参数被替换为 ?(问号)或 ...(省略号)。如果发现这些,则引擎不会调用函数,而是创建一个 Closure,存储函数和提供的参数,并返回该 Closure。Closure 的签名将创建为匹配未指定参数的签名。这意味着可以通过反射检查 Closure,并显示从底层函数派生的所有参数和返回值的完整类型信息。
例如,以下对是逻辑等价的(关于 func_get_args() 的一个例外情况见下文):
// 给定
function foo(int $a, int $b, int $c, int $d): int {
return $a + $b + $c + $d;
}
$f = foo(1, ?, 3, 4);
$f = static fn (int $b): int => foo(1, $b, 3, 4);
$f = foo(1, ?, 3, ?);
$f = static fn (int $b, int $d): int => foo(1, $b, 3, $d);
$f = foo(1, ...);
$f = static fn (int $b, int $c, int $d): int => foo(1, $b, $c, $d);
$f = foo(1, 2, ...);
$f = static fn (int $c, int $d): int => foo(1, 2, $c, $d);
$f = foo(1, ?, 3, ...);
$f = static fn (int $b, int $d): int => foo(1, $b, 3, $d);
占位符语义
本 RFC 引入两个占位符符号:
- 参数占位符
?表示该位置预期正好一个参数。 - 可变参数占位符
...表示该位置可以提供零个或多个参数。
部分函数应用的语法由几个部分组成,按以下顺序(至少需要其中之一以区别于普通函数调用):
- 零个或多个位置值(字面量或变量)与
?混合。 - 零个或多个带值或占位符的命名参数。
- 零个或一个
...符号,表示“任何尚未指定的参数”。
结果 Closure 中的参数和返回值将直接从底层函数继承以下方面:
- 名称
- 类型
- 可选性
- 默认值
- 是否按引用传递/返回
#[NoDiscard]和#[SensitiveParameter]属性的存在(仅这些)
如果函数是可变参数的,则有三个额外规则:
- 任何进入可变参数部分的定位占位符变为必需。
- 如果任何定位占位符进入可变参数部分,则之前剩余的所有占位符变为必需。
- 映射到底层函数可变参数部分的定位占位符将以可变参数名称命名,带有基于 0 的数字后缀。因此
int ...$nums将转换为$nums0、$nums1等,如果需要,可以按名称调用。如果存在同名参数,则会跳过它。
例如:
function foo(
int $a = 5,
int $b = 1,
string ...$c
) {
}
$pfa = foo(?, ?, ?, ?);
// 等价于:
// 注意 $a 和 $b 变为必需,因为必须至少提供 4 个参数。
$pfa = static fn (int $a, int $b, string $c0, string $c1) => foo($a, $b, $c0, $c1);
仅包含这两个属性的原因是它们具有直接的运行时影响。#[SensitiveParameter] 是一个安全问题,因此必须保留。#[NoDiscard] 对于返回值在逻辑上应该传递,因为 PFA 创建的中间 Closure 总是传递底层函数的返回值。相比之下,复制 #[Deprecated] 将导致发出两个警告而不是一个(这是无意义的),而 #[Override] 在 Closure 上没有意义。无法判断用户定义的属性在逻辑上是否值得复制,因为引擎没有上下文知道。我们因此选择忽略它们。
这与 FCC 不同,FCC 保留所有属性。然而,FCC 不像 PFA 那样创建完整的中间 Closure;它只是创建一个对底层函数的特殊引用。
带有值的命名参数将映射到相应命名的参数,无论顺序如何,就像普通调用中的命名参数一样。使用 ? 占位符的命名参数将按出现顺序放入结果 Closure,有效地重新排序这些参数。定位占位符和省略号占位符将遵循原始函数的顺序。
在没有剩余参数的情况下使用 ... 指示是合法的,将生成一个没有必需参数的 Closure,它将使用提供的值调用底层函数。实际上,它成为一种延迟函数调用的方式。这种技术在计算机科学圈中以“thunk”(延迟执行函数)这个令人愉快的名字闻名。
虽然理论上像这样的调用是合法的:
function stuff(int $i, string $s, float $f, Point $p, int $m = 0) {
}
$c = stuff(1, ?, p: ?, f: 3.14, ...);
// 等价于:
$c = static fn (string $s, Point $p, int $m = 0) => stuff(1, $s, 3.14, $p, $m);
实践中,我们预计大多数用法将落入以下三种风格之一:
// 提供除一个参数外的所有参数。 $c = array_map(strtoupper(...), ?); $c = array_filter(?, is_numeric(...)); // 提供一两个值开始,并“其余的”。 $c = stuff(1, 'two', ...); // 用名称填充几个参数,并保留其余部分不变。 $c = stuff(f: 3.14, s: 'two', ...);
示例
通过示例更容易描述语义。以下语句组中的每一组包含逻辑等价的语句。每个的结果是创建一个名为 $c 的可调用对象。(关于 func_get_args() 的例外情况见下文。)
// 普通函数
// 给定:
function stuff(int $i1, string $s2, float$f3, Point $p4, int $m5 = 0): string {
}
// 手动指定所有占位符。
$c = stuff(?, ?, ?, ?, ?);
// 手动指定前两个值,并按原样拉取其余部分。
$c = stuff(?, ?, ...);
$c = static fn (int $i1, string $s2, float$f3, Point $p4, int $m5 = 0): string => stuff($i1, $s2, $f3, $p4, $m5);
// “第一类可调用”退化情况。(自 8.1 起支持)
$c = stuff(...);
$c = static fn (int $i1, string $s2, float$f3, Point $p4, int $m5 = 0): string => stuff($i1, $s2, $f3, $p4, $m5);
// 提供一些值,稍后要求提供其余部分。
$c = stuff(1, 'hi', ?, ?, ?);
$c = stuff(1, 'hi', ...);
$c = static fn (float$f3, Point $p4, int $m5 = 0): string => stuff(1, 'hi', $f3, $p4, $m5);
// 提供一些值,但不只是从左边。
$c = stuff(1, ?, 3.5, ?, ?);
$c = stuff(1, ?, 3.5, ...);
$c = static fn (string $s2, Point $p4, int $m5 = 0): string => stuff(1, $s2, 3.5, $p4, $m5);
// 仅提供最后一个值。
$c = stuff(?, ?, ?, ?, 5);
$c = static fn (int $i1, string $s2, float$f3, Point $p4): string => stuff($i1, $s2, $f3, $p4, 5);
// 不考虑可选参数意味着它将始终获得其默认值。
$c = stuff(?, ?, ?, ?);
$c = static fn (int $i1, string $s2, float$f3, Point $p4): string => stuff($i1, $s2, $f3, $p4);
// 命名参数可以“乱序”拉取,仍然有效。
$c = stuff(?, ?, f3: 3.5, p4: $point);
$c = stuff(?, ?, p4: $point, f3: 3.5);
$c = static fn (int $i1, string $s2): string => stuff($i1, $s2, 3.5, $point);
// 但命名占位符采用列出的顺序。
$c = stuff(s2: ?, i1: ?, p4: ?, f3: 3.5);
$c = static fn (string $s2, int $i1, Point $p4): string => stuff($i1, $s2, 3.5, $p4);
// ... “其余一切”占位符尊重命名参数。
$c = stuff(?, ?, f3: 3.5, p4: $point, ...);
$c = static fn (int $i1, string $s2, int $m5 = 0): string => stuff($i1, $s2, 3.5, $point, $m5);
// 预填充所有参数,创建一个“延迟调用”或“thunk”
$c = stuff(1, 'hi', 3.4, $point, 5, ...);
$c = static fn (): string => stuff(1, 'hi', 3.4, $point, 5);
// 可变参数
// 给定
function things(int $i1, ?float$f3 = null, Point ...$points) {
...
}
// FCC 等价。签名不变。
$c = things(...);
$c = static fn (int $i1, ?float$f3 = null, Point ...$points): string => things(...[$i1, $f3, ...$points]);
// 提供一些值,但允许可变参数保持可变。
$c = things(1, 3.14, ...);
$c = static fn (Point ...$points): string => things(1, 3.14, ...$points);
// 在此版本中,部分应用要求精确四个参数,其中最后两个将通过 things() 的可变参数接收。
// 注意在此情况下 $f 变为必需。
$c = things(?, ?, ?, ?);
$c = static fn (int $i1, ?float$f3, Point $points0, Point $points1): string => things($i1, $f3, $points0, $points1);
注:RFC 文档的剩余部分包括更多高级示例、向后兼容性讨论、未来范围以及投票细节。由于页面较长,这里提供了核心提案和示例的完整翻译。如果需要特定部分的扩展细节,请提供更多指示。
投票信息
根据最新更新,这个 RFC 的投票期为 2025-11-21 至 2025-12-05,目前正在进行中。截至目前,有 31 票赞成、0 票反对(弃权票未指定),已超过 2/3 通过。
看来 PHP 8.6(预计 2026 年发布)确实会引入部分函数应用功能!这将大大提升管道操作符(pipe operator)的可用性,让函数式编程在 PHP 中更流畅。管道操作符的 RFC 也已推进,这俩结合用起来会超级爽——比如直接链式处理数据,而不用写一堆匿名函数。期待明年试用!
RFC:打开站点
以上关于PHP 8.6 将引入部分函数应用,管道操作符 RFC 推进,函数式编程更流畅的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » PHP 8.6 将引入部分函数应用,管道操作符 RFC 推进,函数式编程更流畅

微信
支付宝