匿名递归 PHP 函数
Posted
技术标签:
【中文标题】匿名递归 PHP 函数【英文标题】:Anonymous recursive PHP functions 【发布时间】:2011-01-29 15:02:01 【问题描述】:是否可以有一个递归和匿名的 php 函数?这是我让它工作的尝试,但它没有传入函数名。
$factorial = function( $n ) use ( $factorial )
if( $n <= 1 ) return 1;
return $factorial( $n - 1 ) * $n;
;
print $factorial( 5 );
我也知道这是实现阶乘的不好方法,这只是一个示例。
【问题讨论】:
我没有 PHP 5.3.0 可供检查,但您是否尝试使用global $factorial
?
(sidenote)一个Lamba是一个匿名函数,而上面是一个闭包。
Lambda 和闭包并不相互排斥。事实上,有些人认为闭包必须是 lambda 才能成为闭包(匿名函数)。例如 Python,您必须先为函数命名(取决于版本)。因为你必须给它一个你不能内联的名字,有些人会说它不能成为一个闭包。
print $factorial( 0);
php手册example
【参考方案1】:
使用匿名类(PHP 7+),不定义变量:
echo (new class
function __invoke($n)
return $n < 2 ? 1 : $n * $this($n - 1);
)(5);
【讨论】:
【参考方案2】:您可以在 PHP 7.1+ 中使用 Y Combinator,如下所示:
function Y
($le)
return
(function ($f)
return
$f($f);
)(function ($f) use ($le)
return
$le(function ($x) use ($f)
return
$f($f)($x);
);
);
$le =
function ($factorial)
return
function
($n) use ($factorial)
return
$n < 2 ? $n
: $n * $factorial($n - 1);
;
;
$factorial = Y($le);
echo $factorial(1) . PHP_EOL; // 1
echo $factorial(2) . PHP_EOL; // 2
echo $factorial(5) . PHP_EOL; // 120
玩它:https://3v4l.org/7AUn2
源码来自:https://github.com/whitephp/the-little-phper/blob/master/src/chapter_9.php
【讨论】:
【参考方案3】:在较新版本的 PHP 中,您可以这样做:
$x = function($depth = 0)
if($depth++)
return;
$this($depth);
echo "hi\n";
;
$x = $x->bindTo($x);
$x();
这可能会导致奇怪的行为。
【讨论】:
【参考方案4】:虽然不适合实际使用,但 C 级扩展 mpyw-junks/phpext-callee 提供匿名递归无需分配变量。
<?php
var_dump((function ($n)
return $n < 2 ? 1 : $n * callee()($n - 1);
)(5));
// 5! = 5 * 4 * 3 * 2 * 1 = int(120)
【讨论】:
【参考方案5】:我知道这可能不是一个简单的方法,但我从函数式语言中学到了一种称为"fix" 的技术。 Haskell 的fix
函数更普遍地称为Y combinator,它是最著名的fixed point combinators 之一。
不动点是一个不被函数改变的值:函数的不动点f是任意的x,使得x = f(x)。定点组合子 y 是一个为任何函数 f 返回一个不动点的函数。因为 y(f) 是 f 的一个不动点,所以我们有 y(f) = f(y(f))。
本质上,Y 组合器创建了一个新函数,该函数接受原始函数的所有参数,以及一个附加参数,即递归函数。使用柯里化符号可以更清楚地了解其工作原理。不要将参数写在括号中 (f(x,y,...)
),而是将它们写在函数之后:f x y ...
。 Y 组合子定义为Y f = f (Y f)
;或者,使用递归函数的单个参数,Y f x = f (Y f) x
。
由于 PHP 不会自动生成 curry 函数,所以要让 fix
工作有点麻烦,但我认为这很有趣。
function fix( $func )
return function() use ( $func )
$args = func_get_args();
array_unshift( $args, fix($func) );
return call_user_func_array( $func, $args );
;
$factorial = function( $func, $n )
if ( $n == 1 ) return 1;
return $func( $n - 1 ) * $n;
;
$factorial = fix( $factorial );
print $factorial( 5 );
请注意,这与其他人发布的简单闭包解决方案几乎相同,但函数 fix
会为您创建闭包。定点组合器比使用闭包稍微复杂一些,但更通用,还有其他用途。虽然闭包方法更适合 PHP(它不是一种非常实用的语言),但最初的问题更多的是练习而不是生产,因此 Y 组合器是一种可行的方法。
【讨论】:
值得注意的是,call_user_func_array()
像圣诞节一样慢。
@Xeoncross 与设置陆地速度记录的 PHP 的其余部分相反? :P
注意,您现在 (5.6+) 可以使用参数解包而不是 call_user_func_array
。
@KendallHopkins 为什么会有这个额外的参数array_unshift( $args, fix($func) );
? args 已经满载参数了,真正的递归是由 call_user_func_array() 完成的,那那一行是做什么的呢?【参考方案6】:
为了让它工作,你需要传递 $factorial 作为参考
$factorial = function( $n ) use ( &$factorial )
if( $n == 1 ) return 1;
return $factorial( $n - 1 ) * $n;
;
print $factorial( 5 );
【讨论】:
奇怪的是,bc 对象应该总是通过引用传递,并且 anon。函数是对象... @ellabeauty 在 $factorial 被传递时,它仍然为空(未定义),这就是你必须通过引用传递它的原因。请注意,如果您在调用函数之前修改 $factorial,则结果会随着引用传递而改变。 @ellabeauty:不,你完全误解了它。没有&
的一切都是按价值计算的。 &
的所有内容都是参考。 “对象”在 PHP5 中不是值,不能赋值或传递。您正在处理一个其值为对象引用的变量。和所有变量一样,它可以通过值或引用来捕获,具体取决于是否有&
。
疯了!非常感谢!我怎么到现在才知道这件事?我对递归匿名函数的应用程序数量巨大。现在我终于可以遍历布局中的嵌套结构,而无需显式定义方法并将所有布局数据保留在我的类之外。
就像@barius 说的,在foreach 中使用它时要小心。 $factorial
将在调用函数之前更改,可能会导致奇怪的行为。以上是关于匿名递归 PHP 函数的主要内容,如果未能解决你的问题,请参考以下文章