匿名递归PHP函数


197

是否可以具有递归和匿名的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吗?
kennytm 2010年

5
(旁注) Lamba是匿名函数,而上面是Closure。
戈登

1
Lambda和闭包不是互斥的。实际上,有些人认为闭包必须是lambda才能成为闭包(匿名函数)。例如,您必须先给Python命名函数(取决于版本)。因为您必须给它起一个名字,所以您不能内联,有人会说这使它失去了闭包的资格。
亚当·根特

1
print $factorial( 0);
nickb

php手册示例

Answers:


355

为了使其正常工作,您需要传递$ factorial作为参考

$factorial = function( $n ) use ( &$factorial ) {
    if( $n == 1 ) return 1;
    return $factorial( $n - 1 ) * $n;
};
print $factorial( 5 );

那是奇怪的BC对象,应该总是通过引用和匿名传递。函数是对象...
ellabeauty 2012年

25
在$ factorial传递时@ellabeauty,它仍然为null(未定义),这就是为什么必须通过引用传递它。请注意,如果在调用函数之前修改了$ factorial,结果将随着引用传递而改变。
MariusBalčytis2012年

9
@ellabeauty:不,您完全误解了。一切没有&价值就是价值。的所有内容&均通过引用提供。“对象”不是PHP5中的值,因此无法分配或传递。您正在处理一个变量,其值为对象引用。与所有变量一样,它可以通过值或引用来捕获,具体取决于是否存在&
newacct

3
心灵吹!非常感谢!到现在我怎么还不知道呢?我具有递归匿名函数的应用程序数量巨大。现在,我终于可以遍历布局中的嵌套结构,而不必显式定义一个方法并将所有布局数据保留在类之外。
Dieter Gribnitz 2014年

就像@barius所说的那样,在foreach中使用它时要小心。$factorial会在调用函数之前进行更改,并且可能会导致异常行为。
stil

24

我知道这可能不是一个简单的方法,但是我从功能语言中学到了一种称为“修复”的技术。fixHaskell 的函数通常称为Y组合器,它是最著名的定点组合器之一

一个不动点是一个函数不变的值:一个函数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不会自动咖喱的功能,这是一个黑客位做出的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组合器是一种可行的方法。


10
值得注意的call_user_func_array()是,圣诞节来得很慢。
Xeoncross 2011年

11
@Xeoncross与其他设置陆上速度记录的PHP相反?:P
肯德尔·霍普金斯

1
请注意,您现在可以(5.6+)使用参数解包代替call_user_func_array
法比恩·萨

@KendallHopkins为什么要增加这个参数array_unshift( $args, fix($func) );?Args已经充满了参数,实际的递归是通过call_user_func_array()完成的,那么那行是做什么的?
我想回答

5

尽管它不是用于实际用途,但是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)

0

在新版本的PHP中,您可以执行以下操作:

$x = function($depth = 0) {
    if($depth++)
        return;

    $this($depth);
    echo "hi\n";
};
$x = $x->bindTo($x);
$x();

这有可能导致奇怪的行为。


0

您可以在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


0

使用匿名类(PHP 7+),而没有定义变量:

echo (new class {
    function __invoke($n) {
        return $n < 2 ? 1 : $n * $this($n - 1);
    }
})(5);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.