call_user_func_array
执行“uncurrying”,这与“currying”相反。
以下适用于所有 PHP 的“可调用对象”(命名函数、闭包、方法__invoke
等),因此为简单起见,让我们忽略差异,只关注闭包。
如果我们想接受多个参数,PHP 允许我们使用 3 个不同的 API 来做到这一点。通常的方法是这样的:
$usual = function($a, $b, $c, $d) {
return $a + $b + $c + $d;
};
$result = $usual(10, 20, 30, 40); // $result == 100
另一种方式称为柯里化形式:
$curried = function($a) {
return function($b) use ($a) {
return function($c) use ($a, $b) {
return function($d) use ($a, $b, $c) {
return $a + $b + $c + $d;
};
};
};
};
$result = call_user_func(
call_user_func(
call_user_func(
$curried(10),
20),
30),
40); // $result == 100
优点是所有柯里化函数都可以以相同的方式调用:给它们一个参数。
如果需要更多参数,则返回更多 curried 函数,这些函数会“记住”先前的参数。这允许我们现在传入一些参数,其余的稍后传递。
这有一些问题:
- 显然,以这种方式编写和调用函数非常繁琐。
- 如果我们提供柯里化函数,当不需要它们的“记忆”能力时,它们会很尴尬。
- 如果我们依赖柯里化函数的“记忆”能力,当其他人的代码不提供它时,我们会感到失望。
我们可以通过使用转换函数来解决所有这些问题(免责声明:这是我的博客)。这让我们可以以通常的方式编写和调用我们的函数,但赋予它们相同的“记忆”能力,就好像它们被柯里化了一样:
$curried = curry(function($a, $b, $c, $d) {
return $a + $b + $c + $d;
});
$result1 = $curried(10, 20, 30, 40); // $result1 = 100
$result2 = call_user_func($curried(10, 20), 30, 40); // $result2 = 100
第三种方法称为uncurried并将其所有参数合二为一:
$uncurried = function($args) {
return $args[0] + $args[1] + $args[2] + $args[3];
};
$result = $uncurried([10, 20, 30, 40]); // $result == 100
就像柯里化函数一样,非柯里化函数都可以用一个参数调用,尽管这次它是一个数组。我们仍然面临着与柯里化函数相同的兼容性问题:如果我们选择使用非柯里化函数,我们就不能指望其他人都选择相同的。因此,我们还需要一个非柯里化的转换函数。这就是call_user_func_array
:
$uncurried = function($args) use ($usual) {
return call_user_func_array($usual, $args);
};
$result1 = $usual(10, 20, 30, 40); // $result1 = 100
$result2 = $uncurried([10, 20, 30, 40]); // $result2 = 100
有趣的是,我们可以通过 currying 摆脱那个额外的function($args)
包装器(一个称为“eta-reduction”的过程)call_user_func_array
:
$uncurried = curry('call_user_func_array', $usual);
$result = $uncurried([10, 20, 30, 40]); // $result == 100
不幸call_user_func_array
的是,它不如curry
; 它不会自动在两者之间转换。我们可以编写自己的uncurry
具有这种能力的函数:
function uncurry($f)
{
return function($args) use ($f) {
return call_user_func_array(
$f,
(count(func_get_args()) > 1)? func_get_args()
: $args);
};
}
$uncurried = uncurry($usual);
$result1 = $uncurried(10, 20, 30, 40); // $result1 == 100
$result2 = $uncurried([10, 20, 30, 40]); // $result2 == 100
这些转换函数表明 PHP 的“通常”定义函数的方式实际上是多余的:如果我们用“智能”curried 或 uncurried 函数替换 PHP 的“通常”函数,大量代码将继续工作。如果我们这样做了,最好对所有内容都进行咖喱,并根据需要选择性地取消咖喱,因为这比反过来更容易。
不幸的是,一些期望使用可变数量的参数的东西func_get_args
会破坏,以及具有默认参数值的函数。
有趣的是,默认值只是柯里化的一种特殊形式。如果我们将这些参数放在第一位而不是最后一位,我们基本上可以不用它们,并提供一堆替代定义,这些定义在默认值中使用。例如:
$defaults = function($a, $b, $c = 30, $d = 40) {
return $a + $b + $c + $d;
};
$def1 = $defaults(10, 20, 30, 40); // $def1 == 100
$def2 = $defaults(10, 20, 30); // $def2 == 100
$def3 = $defaults(10, 20); // $def3 == 100
$curried = function($d, $c, $a, $b) {
return $a + $b + $c + $d;
};
$curriedD = $curried(40);
$curriedDC = $curriedD(30);
$cur1 = $curried(10, 20, 30, 40); // $cur1 == 100
$cur2 = $curriedD(10, 20, 30); // $cur2 == 100
$cur3 = $curriedDC(10, 20); // $cur3 == 100