2010-03-19 29 views
150

¿Es posible tener una función PHP recursiva y anónima? Este es mi intento de hacer que funcione, pero no pasa el nombre de la función.Funciones PHP recursivas anónimas

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

También soy consciente de que esta es una mala forma de implementar factorial, es solo un ejemplo.

+0

No tengo PHP 5.3.0 para verificar, pero ¿intentó usar 'global $ factorial'? – kennytm

+4

* (nota al margen) * a Lamba es una función anónima, mientras que la anterior es un Cierre. – Gordon

+1

Lambdas y Closures no son mutuamente excluyentes. De hecho, algunas personas creen que un cierre tiene que ser lambda para que sea un cierre (función anónima). Por ejemplo, Python tiene que darle un nombre a la función primero (dependiendo de la versión).Porque tiene que darle un nombre que no puede alinear y algunos dirán que lo descalifica para que no sea un cierre. –

Respuesta

280

Con el fin de que funcione, tiene que pasar $ factorial como referencia

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

+1, consulte también http: //php100.wordpress .com/2009/04/13/php-y-combinator/ – user187291

+0

es raro que los objetos bc siempre se pasen por referencia, y anon. las funciones son objetos ... – ellabeauty

+20

@ellabeauty en el momento en que se pasa $ factorial, sigue siendo nulo (no definido), es por eso que debe pasarlo por referencia. Tenga en cuenta que si modifica $ factorial antes de llamar a la función, el resultado cambiará a medida que se transfiera por referencia. –

22

Sé que esto podría no ser un enfoque simple, pero he aprendido acerca de una técnica llamada "fix" de los lenguajes funcionales. La función fix de Haskell se conoce generalmente como Y combinator, que es una de las más conocidas fixed point combinators.

un punto fijo es un valor que no se ha modificado por una función de: un punto fijo de una función f es cualquier x tal que x =     f (x). Un combinador de punto fijo y es una función que devuelve un punto fijo para cualquier función f. Como y (f) es un punto fijo de f, tenemos y (f)   =   f (y (f)).

Esencialmente, el combinador Y crea una nueva función que toma todos los argumentos del original, más un argumento adicional que es la función recursiva. Cómo funciona esto es más obvio usando la notación al curry. En lugar de escribir argumentos entre paréntesis (f(x,y,...)), escríbalos después de la función: f x y .... El combinador Y se define como Y f = f (Y f); o, con un único argumento para la función recursada, Y f x = f (Y f) x.

Dado que PHP no funciona automáticamente con curry, es un poco complicado hacer fix, pero creo que es interesante.

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 esto es casi lo mismo que las soluciones de cierre simples otros han publicado, pero la función fix crea el cierre para usted. Los combinadores de punto fijo son un poco más complejos que usar un cierre, pero son más generales y tienen otros usos. Si bien el método de cierre es más adecuado para PHP (que no es un lenguaje terriblemente funcional), el problema original es más un ejercicio que para la producción, por lo que el combinador Y es un enfoque viable.

+8

Vale la pena señalar que 'call_user_func_array()' es lento como Navidad. – Xeoncross

+10

@Xeoncross ¿A diferencia del resto de PHP que está configurando el registro de velocidad terrestre? : P –

+1

Nota, ahora puede (5.6+) usar el desempaquetado de argumentos en lugar de 'call_user_func_array'. –

2

Aunque no es para uso práctico, la extensión de nivel C mpyw-junks/phpext-callee proporciona la recursión anónima sin asignar variables.

<?php 

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

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

En las nuevas versiones de PHP se puede hacer esto:

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

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

Potencialmente, esto puede conducir a un comportamiento extraño.