2010-03-19 24 views
150

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

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

Sono anche consapevole del fatto che questo è un brutto modo per implementare fattoriale, è solo un esempio.

+0

Non ho PHP 5.3.0 da verificare, ma hai provato a utilizzare 'global $ factorial'? – kennytm

+4

* (sidenote) * una Lamba è una funzione anonima, mentre quella precedente è una chiusura. – Gordon

+1

Lambda e chiusure non si escludono a vicenda. In effetti alcune persone credono che una chiusura deve essere lambda perché sia ​​una chiusura (funzione anonima). Ad esempio in Python è necessario assegnare prima un nome alla funzione (a seconda della versione).Perché devi dargli un nome che non puoi in linea e qualcuno direbbe che lo squalifica dall'essere una chiusura. –

risposta

280

Al fine di farlo funzionare, è necessario passare $ fattoriale come riferimento

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

+1, vedere anche http: //php100.wordpress .com/2009/04/13/php-y-combinator/ – user187291

+0

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

+20

@ellabeauty nel tempo in cui viene passato $ fattoriale, è ancora nullo (non definito), ecco perché è necessario passarlo per riferimento. Attenzione, se modifichi il $ fattoriale prima di chiamare la funzione, il risultato cambierà quando viene passato per riferimento. –

22

So che questo potrebbe non essere un approccio semplice, ma ho imparato a conoscere una tecnica chiamata "fix" da linguaggi funzionali. La funzione fix di Haskell è conosciuta più in generale come Y combinator, che è uno dei più noti fixed point combinators.

Un punto fisso è un valore che è invariata da una funzione: un punto fisso di una funzione f è qualunque x tale che x =     f (x). Un combinatore a virgola fissa 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 prende tutti gli argomenti dell'originale, più un argomento aggiuntivo che è la funzione ricorsiva. Come funziona è più ovvio usare la notazione al curry. Invece di scrivere argomenti tra parentesi (f(x,y,...)), scriverli dopo la funzione: f x y .... Il combinatore Y è definito come Y f = f (Y f); oppure, con un singolo argomento per la funzione ricorsiva, Y f x = f (Y f) x.

Dal momento che PHP non esegue automaticamente le funzioni curry, è un po 'un trucco per rendere operativo il lavoro fix, 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 questa è quasi la stessa di semplici soluzioni di chiusura altri hanno postato, ma la funzione fix crea la chiusura per voi. I combinatori a punti fissi sono leggermente più complessi rispetto all'utilizzo 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ù di un esercizio che di produzione, quindi il combinatore Y è un approccio praticabile.

+8

Vale la pena notare che 'call_user_func_array()' è lento come Natale. – Xeoncross

+10

@Xeoncross Al contrario del resto di PHP che sta impostando il record di velocità terrestre? : P –

+1

Nota, ora è possibile (5.6+) usare l'argomento decompressione invece di 'call_user_func_array'. –

2

Sebbene non sia per uso pratico, l'estensione a livello C mpyw-junks/phpext-callee fornisce la ricorsione anonima senza assegnare le 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 si può fare questo:

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

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

Ciò può potenzialmente portare a un comportamento strano.