Anonyme rekursive PHP-Funktionen

197

Ist es möglich, eine rekursive und anonyme PHP-Funktion zu haben? Dies ist mein Versuch, es zum Laufen zu bringen, aber es wird der Funktionsname nicht übergeben.

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

Mir ist auch bewusst, dass dies ein schlechter Weg ist, Fakultät zu implementieren, es ist nur ein Beispiel.

Kendall Hopkins
quelle
Ich muss PHP 5.3.0 nicht überprüfen, aber haben Sie es versucht global $factorial?
Kennytm
5
(Nebenbemerkung) Eine Lamba ist eine anonyme Funktion, während die oben genannte eine Schließung ist.
Gordon
1
Lambdas und Closures schließen sich nicht gegenseitig aus. Tatsächlich glauben einige Leute, dass eine Schließung Lambda sein muss, damit sie eine Schließung ist (anonyme Funktion). Zum Beispiel müssen Sie Python der Funktion zuerst einen Namen geben (abhängig von der Version). Weil Sie ihm einen Namen geben müssen, den Sie nicht inline können, und einige würden sagen, dass dies ihn von einer Schließung ausschließt.
Adam Gent
1
print $factorial( 0);
Nickb
PHP Handbuch Beispiel

Antworten:

355

Damit es funktioniert, müssen Sie $ factorial als Referenz übergeben

$factorial = function( $n ) use ( &$factorial ) {
    if( $n == 1 ) return 1;
    return $factorial( $n - 1 ) * $n;
};
print $factorial( 5 );
Derek H.
quelle
Das ist seltsam, denn Objekte sollten immer als Referenz und anon übergeben werden. Funktionen sind Objekte ...
Ellabeauty
25
@ellabeauty in der Zeit, in der $ Fakultät übergeben wird, ist es immer noch null (nicht definiert), deshalb müssen Sie es als Referenz übergeben. Beachten Sie, dass sich das Ergebnis ändert, wenn Sie die $ Fakultät vor dem Aufrufen der Funktion ändern, wenn sie als Referenz übergeben wird.
Marius Balčytis
9
@ellabeauty: Nein, du verstehst das völlig falsch. Alles ohne &ist von Wert. Alles mit &ist durch Bezugnahme. "Objekte" sind in PHP5 keine Werte und können nicht zugewiesen oder übergeben werden. Sie haben es mit einer Variablen zu tun, deren Wert eine Objektreferenz ist. Wie alle Variablen kann es nach Wert oder Referenz erfasst werden, je nachdem, ob es eine gibt &.
Newacct
3
Verblüfft! Vielen Dank! Woher wusste ich das bis jetzt nicht? Die Menge an Anwendungen, die ich für rekursive anonyme Funktionen habe, ist riesig. Jetzt kann ich endlich verschachtelte Strukturen in Layouts durchlaufen, ohne explizit eine Methode definieren zu müssen, und alle meine Layoutdaten aus meinen Klassen heraushalten.
Dieter Gribnitz
Wie @barius sagte, vorsichtig, wenn Sie es in foreach verwenden. $factorialwird vor dem Aufruf der Funktion geändert und kann zu seltsamem Verhalten führen.
Noch
24

Ich weiß, dass dies möglicherweise kein einfacher Ansatz ist, aber ich habe aus funktionalen Sprachen etwas über eine Technik namens "Fix" gelernt . Die fixFunktion von Haskell ist allgemeiner als der Y-Kombinator bekannt , der einer der bekanntesten Festpunktkombinatoren ist .

Ein Festpunkt ist ein Wert, der von einer Funktion nicht geändert wird : Ein Festpunkt einer Funktion f ist ein beliebiges x, so dass x = f (x). Ein Festpunktkombinator y ist eine Funktion, die für jede Funktion f einen Festpunkt zurückgibt. Da y (f) ein fester Punkt von f ist, haben wir y (f) = f (y (f)).

Im Wesentlichen erstellt der Y-Kombinator eine neue Funktion, die alle Argumente des Originals sowie ein zusätzliches Argument verwendet, das die rekursive Funktion ist. Wie dies funktioniert, ist unter Verwendung der Curry-Notation offensichtlicher. Anstatt Argumente in Klammern ( f(x,y,...)) zu schreiben, schreiben Sie sie nach der Funktion : f x y .... Der Y-Kombinator ist definiert als Y f = f (Y f); oder mit einem einzigen Argument für die rekursive Funktion , Y f x = f (Y f) x.

Da PHP nicht automatisch Curry- Funktionen verwendet, ist es ein bisschen schwierig, die fixArbeit zum Laufen zu bringen , aber ich finde es 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 );

Beachten Sie, dass dies fast das gleiche ist wie die einfachen Verschlusslösungen, die andere veröffentlicht haben, aber die Funktion fixerstellt den Verschluss für Sie. Festkomma-Kombinatoren sind etwas komplexer als die Verwendung eines Verschlusses, jedoch allgemeiner und haben andere Verwendungszwecke. Während die Verschlussmethode besser für PHP geeignet ist (was keine besonders funktionale Sprache ist), ist das ursprüngliche Problem eher eine Übung als für die Produktion, sodass der Y-Kombinator ein praktikabler Ansatz ist.

Kendall Hopkins
quelle
10
Es ist erwähnenswert, dass call_user_func_array()Weihnachten so langsam ist.
Xeoncross
11
@Xeoncross Im Gegensatz zum Rest von PHP, das den Geschwindigkeitsrekord auf dem Land aufstellt? : P
Kendall Hopkins
1
Beachten Sie, dass Sie jetzt (5.6+) das Entpacken von Argumenten anstelle von verwenden können call_user_func_array.
Fabien Sa
@ KendallHopkins warum diese zusätzlichen Argumente array_unshift( $args, fix($func) );? Args ist bereits mit den Parametern beladen, und die eigentliche Rekursion erfolgt durch call_user_func_array (). Was macht diese Zeile also?
Ich möchte Antworten
5

Obwohl es nicht für den praktischen Gebrauch gedacht ist, bietet die C-Level-Erweiterung mpyw-junks / phpext-callee eine anonyme Rekursion ohne Zuweisung von Variablen .

<?php

var_dump((function ($n) {
    return $n < 2 ? 1 : $n * callee()($n - 1);
})(5));

// 5! = 5 * 4 * 3 * 2 * 1 = int(120)
mpyw
quelle
0

In neueren Versionen von PHP können Sie dies tun:

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

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

Dies kann möglicherweise zu seltsamem Verhalten führen.

jgmjgm
quelle
0

Sie können Y Combinator in PHP 7.1+ wie folgt verwenden:

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

Spielen Sie damit: https://3v4l.org/7AUn2

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

Lei Fan
quelle
0

Mit einer anonymen Klasse (PHP 7+), ohne eine Variable zu definieren:

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