Funzioni PHP ricorsive anonime


197

È possibile avere una funzione PHP sia ricorsiva che anonima? Questo è il mio tentativo di farlo funzionare, ma non passa nel nome della funzione.

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

Sono anche consapevole che questo è un brutto modo di implementare fattoriale, è solo un esempio.


Non ho PHP 5.3.0 da controllare ma hai provato a usarlo global $factorial?
kennytm,

5
(sidenote) una Lamba è una funzione anonima, mentre quanto sopra è una chiusura.
Gordon,

1
Lambdas e Closures non si escludono a vicenda. In effetti alcune persone credono che una chiusura debba essere lambda perché sia ​​una chiusura (funzione anonima). Ad esempio Python devi prima dare un nome alla funzione (a seconda della versione). Perché devi dargli un nome che non puoi incorporare e alcuni direbbero che lo squalifica dall'essere una chiusura.
Adam Gent,

1
print $factorial( 0);
Nick

Risposte:


356

Affinché funzioni, è necessario passare $ factorial come riferimento

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

è strano che gli oggetti bc debbano sempre essere passati per riferimento e anon. le funzioni sono oggetti ...
ellabeauty

25
@ellabeauty nel tempo in cui $ factorial è passato, è ancora nullo (non definito), ecco perché devi passarlo per riferimento. Tenere presente che se si modifica $ factorial prima di chiamare la funzione, il risultato cambierà quando viene passato per riferimento.
Marius Balčytis,

9
@ellabeauty: No, lo hai completamente frainteso. Tutto senza &è per valore. Tutto con &è per riferimento. "Oggetti" non sono valori in PHP5 e non possono essere assegnati o passati. Hai a che fare con una variabile il cui valore è un riferimento ad oggetto. Come tutte le variabili, può essere catturata per valore o per riferimento, a seconda che esista un &.
nuovo

3
Sbalordire! Molte grazie! Come facevo a non saperlo fino ad ora? La quantità di applicazioni che ho per le funzioni anonime ricorsive è enorme. Ora posso finalmente passare in rassegna le strutture nidificate nei layout senza dover definire esplicitamente un metodo e tenere tutti i miei dati di layout fuori dalle mie classi.
Dieter Gribnitz,

Come ha detto @barius, fai attenzione quando lo usi in foreach. $factorialverrà modificato prima che la funzione sia stata chiamata e potrebbe comportare un comportamento strano.
stil

24

So che questo potrebbe non essere un approccio semplice, ma ho imparato una tecnica chiamata "correzione" dai linguaggi funzionali. La fixfunzione di Haskell è conosciuta più in generale come il combinatore Y , che è uno dei più noti combinatori a punto fisso .

Un punto fisso è un valore invariato da una funzione: un punto fisso di una funzione f è qualsiasi x tale che x = f (x). Un combinatore a punto fisso y è una funzione che restituisce un punto fisso per qualsiasi funzione f. Poiché y (f) è un punto fisso di f, abbiamo y (f) = f (y (f)).

In sostanza, il combinatore Y crea una nuova funzione che accetta tutti gli argomenti dell'originale, oltre a un argomento aggiuntivo che è la funzione ricorsiva. Come funziona è più ovvio usando la notazione al curry. Invece di scrivere argomenti tra parentesi ( f(x,y,...)), scriverle dopo la funzione: f x y .... Il combinatore Y è definito come Y f = f (Y f); o, con un singolo argomento della funzione ricorsivo, Y f x = f (Y f) x.

Dato che PHP non carica automaticamente le funzioni, è un po 'un trucco per far fixfunzionare, ma penso che sia interessante.

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

Nota che è quasi uguale alle semplici soluzioni di chiusura che altri hanno pubblicato, ma la funzione fixcrea la chiusura per te. I combinatori a punto fisso sono leggermente più complessi rispetto all'uso di una chiusura, ma sono più generali e hanno altri usi. Mentre il metodo di chiusura è più adatto per PHP (che non è un linguaggio terribilmente funzionale), il problema originale è più un esercizio che per la produzione, quindi il combinatore Y è un approccio praticabile.


10
Vale la pena notare che call_user_func_array()è lento come il Natale.
Xeoncross,

11
@Xeoncross A differenza del resto di PHP che sta stabilendo il record di velocità terrestre? : P
Kendall Hopkins,

1
Nota, ora puoi (5.6+) usare l'argomento unpacking invece di call_user_func_array.
Fabien Sa

@KendallHopkins perché questi argomenti aggiuntivi array_unshift( $args, fix($func) );? Args è già carico di parametri e la ricorsione effettiva viene effettuata da call_user_func_array (), quindi cosa fa quella linea?
Voglio le risposte il

5

Sebbene non sia per uso pratico, l'estensione di livello C mpyw-junks / phpext-callee fornisce una ricorsione anonima senza assegnare variabili .

<?php

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

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

0

Nelle versioni più recenti di PHP puoi farlo:

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

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

Questo può potenzialmente portare a comportamenti strani.


0

Puoi usare Y Combinator in PHP 7.1+ come di seguito:

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

Gioca con esso: https://3v4l.org/7AUn2

Codici sorgente da: https://github.com/whitephp/the-little-phper/blob/master/src/chapter_9.php


0

Con una classe anonima (PHP 7+), senza definire una variabile:

echo (new class {
    function __invoke($n) {
        return $n < 2 ? 1 : $n * $this($n - 1);
    }
})(5);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.