Fonctions PHP récursives anonymes

197

Est-il possible d'avoir une fonction PHP à la fois récursive et anonyme? C'est ma tentative pour le faire fonctionner, mais il ne transmet pas le nom de la fonction.

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

Je suis également conscient que c'est une mauvaise façon de mettre en œuvre factorielle, c'est juste un exemple.

Kendall Hopkins
la source
Je n'ai pas PHP 5.3.0 à vérifier mais avez-vous essayé d'utiliser global $factorial?
kennytm
5
(sidenote) un Lamba est une fonction anonyme, tandis que ce qui précède est une fermeture.
Gordon
1
Les lambdas et les fermetures ne s'excluent pas mutuellement. En fait, certaines personnes pensent qu'une fermeture doit être lambda pour que ce soit une fermeture (fonction anonyme). Par exemple, Python vous devez d'abord donner un nom à la fonction (selon la version). Parce que vous devez lui donner un nom que vous ne pouvez pas aligner et certains diraient que cela le disqualifie d'être une fermeture.
Adam Gent
1
print $factorial( 0);
nickb
php Manual example

Réponses:

356

Pour que cela fonctionne, vous devez passer $ factorial comme référence

$factorial = function( $n ) use ( &$factorial ) {
    if( $n == 1 ) return 1;
    return $factorial( $n - 1 ) * $n;
};
print $factorial( 5 );
Derek H
la source
c'est bizarre les objets bc devraient toujours être passés par référence, et anon. les fonctions sont des objets ...
ellabeauty
25
@ellabeauty au moment où $ factorial est passé, il est toujours nul (non défini), c'est pourquoi vous devez le passer par référence. Sachez que si vous modifiez la factorielle $ avant d'appeler la fonction, le résultat changera au fur et à mesure qu'il sera transmis par référence.
Marius Balčytis
9
@ellabeauty: Non, vous le comprenez complètement mal. Tout ce qui &est sans valeur. Tout &est par référence. Les "objets" ne sont pas des valeurs en PHP5 et ne peuvent pas être attribués ou transmis. Vous avez affaire à une variable dont la valeur est une référence d'objet. Comme toutes les variables, il peut être capturé par valeur ou par référence, selon qu'il existe un &.
newacct
3
L'esprit soufflé! Merci beaucoup! Comment ne l'avais-je pas su jusqu'à présent? La quantité d'application que j'ai pour les fonctions anonymes récursives est énorme. Maintenant, je peux enfin parcourir les structures imbriquées dans les mises en page sans avoir à définir explicitement une méthode et à garder toutes mes données de mise en page hors de mes classes.
Dieter Gribnitz
Comme l'a dit @barius, soyez prudent lorsque vous l'utilisez dans foreach. $factorialsera modifié avant l'appel de la fonction et il peut en résulter un comportement étrange.
jusqu'au
24

Je sais que ce n'est peut-être pas une approche simple, mais j'ai appris une technique appelée «correction» à partir de langages fonctionnels. La fixfonction de Haskell est plus généralement connue sous le nom de combinateur Y , qui est l'un des combinateurs à point fixe les plus connus .

Un point fixe est une valeur inchangée par une fonction: un point fixe d'une fonction f est tout x tel que x = f (x). Un combinateur à point fixe y est une fonction qui renvoie un point fixe pour toute fonction f. Puisque y (f) est un point fixe de f, nous avons y (f) = f (y (f)).

Essentiellement, le combinateur Y crée une nouvelle fonction qui prend tous les arguments de l'original, plus un argument supplémentaire qui est la fonction récursive. Comment cela fonctionne est plus évident en utilisant la notation au curry. Au lieu d'écrire des arguments entre parenthèses ( f(x,y,...)), les écrire après la fonction: f x y .... Le combinateur Y est défini comme Y f = f (Y f); ou, avec un seul argument pour la fonction récursive,Y f x = f (Y f) x .

Depuis PHP ne curry pas automatiquement les fonctions, c'est un peu un hack pour faire fixfonctionner, mais je pense que c'est intéressant.

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

Notez que c'est presque la même chose que les solutions de fermeture simples que d'autres ont publiées, mais la fonction fixcrée la fermeture pour vous. Les combinateurs à virgule fixe sont légèrement plus complexes que l'utilisation d'une fermeture, mais sont plus généraux et ont d'autres utilisations. Alors que la méthode de fermeture convient mieux à PHP (qui n'est pas un langage terriblement fonctionnel), le problème d'origine est plus un exercice que pour la production, donc le combinateur Y est une approche viable.

Kendall Hopkins
la source
10
Il convient de noter que call_user_func_array()c'est lent comme Noël.
Xeoncross
11
@Xeoncross Par opposition au reste de PHP qui établit le record de vitesse terrestre? : P
Kendall Hopkins
1
Remarque, vous pouvez désormais (5.6+) utiliser le déballage des arguments à la place de call_user_func_array.
Fabien Sa
@KendallHopkins pourquoi ces arguments supplémentaires array_unshift( $args, fix($func) );? Args est déjà chargé avec les paramètres, et la récursivité réelle est effectuée par call_user_func_array (), alors que fait cette ligne?
Je veux des réponses
5

Bien que ce ne soit pas pour une utilisation pratique, l'extension de niveau C mpyw-junks / phpext-callee fournit une récursivité anonyme sans affecter de variables .

<?php

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

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

Dans les versions plus récentes de PHP, vous pouvez le faire:

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

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

Cela peut potentiellement conduire à un comportement étrange.

jgmjgm
la source
0

Vous pouvez utiliser Y Combinator en PHP 7.1+ comme ci-dessous:

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

Jouez avec: https://3v4l.org/7AUn2

Codes source de: https://github.com/whitephp/the-little-phper/blob/master/src/chapter_9.php

Lei Fan
la source
0

Avec une classe anonyme (PHP 7+), sans définir de variable:

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