匿名递归 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:不,你完全误解了它。没有&amp; 的一切都是按价值计算的。 &amp; 的所有内容都是参考。 “对象”在 PHP5 中不是值,不能赋值或传递。您正在处理一个其值为对象引用的变量。和所有变量一样,它可以通过值或引用来捕获,具体取决于是否有&amp; 疯了!非常感谢!我怎么到现在才知道这件事?我对递归匿名函数的应用程序数量巨大。现在我终于可以遍历布局中的嵌套结构,而无需显式定义方法并将所有布局数据保留在我的类之外。 就像@barius 说的,在foreach 中使用它时要小心。 $factorial 将在调用函数之前更改,可能会导致奇怪的行为。

以上是关于匿名递归 PHP 函数的主要内容,如果未能解决你的问题,请参考以下文章

PHP使用匿名函数递归调用(闭包)

php 在 匿名函数中 调用自身。。

匿名函数和闭包规避xdebug限制的函数递归深度限制

七 递归与二分法匿名函数内置函数

php闭包实现函数的自调用,也是递归

递归函数匿名函数