call_user_func_array
执行“取消柯里化”操作,这是“柯里化”的反义词。
以下内容适用于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);
优点是所有柯里化函数都可以以相同的方式调用:只需给它们一个参数。
如果需要更多的参数,就会返回更多的柯里化函数,这些函数“记住”了先前的参数。这使我们可以现在传入一些参数,稍后再传入其余的参数。
这样做存在一些问题:
- 显然,用这种方式编写和调用函数非常繁琐。
- 如果我们提供柯里化函数,当它们的“记忆”能力不需要时,它们会很笨拙。
- 如果我们依赖于柯里化函数的“记忆”能力,当其他人的代码没有提供它时,我们会感到失望。
我们可以通过使用
转换函数(免责声明:那是我的博客)来解决所有这些问题。这使我们可以按照通常的方式编写和调用函数,但是赋予它们与柯里化函数相同的“记忆”能力。
$curried = curry(function($a, $b, $c, $d) {
return $a + $b + $c + $d;
});
$result1 = $curried(10, 20, 30, 40);
$result2 = call_user_func($curried(10, 20), 30, 40);
第三种方式被称为“非柯里化”,它将所有参数一次性传入:
$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
有趣的是,我们可以通过柯里化
call_user_func_array
来消除多余的
function($args)
包装(这个过程被称为“Eta-Reduction”)。
$uncurried = curry('call_user_func_array', $usual);
$result = $uncurried([10, 20, 30, 40]);
不幸的是,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);
$result2 = $uncurried([10, 20, 30, 40]);
这些转换函数表明了PHP定义函数的“通常”方式实际上是多余的:如果我们用“聪明”的柯里化或非柯里化函数替换PHP的“通常”函数,很多代码仍然可以正常工作。如果这样做,最好将所有东西都柯里化,并根据需要选择非柯里化,因为这比相反的情况更容易。
不幸的是,一些使用
func_get_args
期望变量数量的东西以及具有默认参数值的函数会出现问题。
有趣的是,默认值只是柯里化的一种特殊形式。如果我们将那些参数放在第一个位置而不是最后,并且提供一堆备选定义来在默认情况下进行柯里化,我们大多数情况下可以不用它们。例如:
$defaults = function($a, $b, $c = 30, $d = 40) {
return $a + $b + $c + $d;
};
$def1 = $defaults(10, 20, 30, 40);
$def2 = $defaults(10, 20, 30);
$def3 = $defaults(10, 20);
$curried = function($d, $c, $a, $b) {
return $a + $b + $c + $d;
};
$curriedD = $curried(40);
$curriedDC = $curriedD(30);
$cur1 = $curried(10, 20, 30, 40);
$cur2 = $curriedD(10, 20, 30);
$cur3 = $curriedDC(10, 20);
call_user_func_array
主要用于动态情境,当你事先不知道要调用哪个函数和传递哪些参数时使用。它可以帮助你在运行时决定要调用哪个函数和传递哪些参数。 - knittl