Anonieme recursieve PHP-functies

Is het mogelijk om een ​​PHP-functie te hebben die zowel recursief als anoniem is? Dit is mijn poging om het werkend te krijgen, maar het komt niet door in de functienaam.

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

Ik ben me er ook van bewust dat dit een slechte manier is om faculteit te implementeren, het is slechts een voorbeeld.


Antwoord 1, autoriteit 100%

Om het te laten werken, moet je $factorial als referentie doorgeven

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

Antwoord 2, autoriteit 6%

Ik weet dat dit misschien geen eenvoudige benadering is, maar ik heb geleerd over een techniek genaamd “fix” uit functionele talen. De functie fixvan Haskell is meer algemeen bekend als de Y-combinator, een van de meest bekende vaste punt combinators.

Een vast punt is een waarde die onveranderd blijft door een functie: een vast punt van een functie fis een xzodanig dat x = f(x ). Een vaste-kombinator yis een functie die een vast punt retourneert voor elke functie f. Aangezien y(f) een vast punt van f is, hebben we y(f) = f(y(f)).

In wezen creëert de Y-combinator een nieuwe functie die alle argumenten van het origineel overneemt, plus een extra argument dat de recursieve functie is. Hoe dit werkt, wordt duidelijker met behulp van curried-notatie. In plaats van argumenten tussen haakjes te schrijven (f(x,y,...)), schrijf ze dan achter de functie: f x y .... De Y-combinator wordt gedefinieerd als Y f = f (Y f); of, met een enkel argument voor de recursieve functie, Y f x = f (Y f) x.

Aangezien PHP niet automatisch curry-functies uitvoert, is het een beetje een hack om fixwerkt, maar ik vind het interessant.

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 );

Merk op dat dit bijna hetzelfde is als de eenvoudige sluitingsoplossingen die anderen hebben gepost, maar de functie fixmaakt de sluiting voor u aan. Vaste punt combinators zijn iets complexer dan het gebruik van een sluiting, maar zijn algemener en hebben andere toepassingen. Hoewel de sluitingsmethode meer geschikt is voor PHP (wat geen erg functionele taal is), is het oorspronkelijke probleem meer een oefening dan voor productie, dus de Y-combinator is een haalbare benadering.


Antwoord 3

Hoewel het niet voor praktisch gebruik is, is de C-level extensie mpyw-junks/phpext-callee biedt anonieme recursie zonder variabelen toe te wijzen.

<?php
var_dump((function ($n) {
    return $n < 2 ? 1 : $n * callee()($n - 1);
})(5));
// 5! = 5 * 4 * 3 * 2 * 1 = int(120)

Antwoord 4

In nieuwere versies van PHP kun je dit doen:

$x = function($depth = 0) {
    if($depth++)
        return;
    $this($depth);
    echo "hi\n";
};
$x = $x->bindTo($x);
$x();

Dit kan mogelijk leiden tot vreemd gedrag.


Antwoord 5

Je kunt Y Combinator gebruiken in PHP 7.1+ zoals hieronder:

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

Speel ermee: https://3v4l.org/7AUn2

Broncodes van: https://github .com/whitephp/the-little-phper/blob/master/src/chapter_9.php


Antwoord 6

Met een anonieme klasse (PHP 7+), zonder een variabele te definiëren:

echo (new class {
    function __invoke($n) {
        return $n < 2 ? 1 : $n * $this($n - 1);
    }
})(5);

Other episodes