Anonimowe rekurencyjne funkcje PHP

197

Czy można mieć funkcję PHP zarówno rekurencyjną, jak i anonimową? To jest moja próba uruchomienia go, ale nie przechodzi w nazwie funkcji.

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

Wiem też, że to zły sposób na wdrożenie silni, to tylko przykład.

Kendall Hopkins
źródło
Nie mam PHP 5.3.0 do sprawdzenia, ale próbowałeś użyć global $factorial?
kennytm,
5
(sidenote) Lamba jest funkcją anonimową, a powyższe jest zamknięciem.
Gordon,
1
Jagnięta i zamknięcia nie wykluczają się wzajemnie. W rzeczywistości niektórzy uważają, że zamknięcie musi być lambda, aby było zamknięciem (funkcja anonimowa). Na przykład w Pythonie musisz najpierw nadać tej funkcji nazwę (w zależności od wersji). Ponieważ musisz nadać mu nazwę, której nie możesz wpisać, a niektórzy powiedzą, że dyskwalifikuje to od zamknięcia.
Adam Gent
1
print $factorial( 0);
nickb 20.04.2013
Podręcznik PHP przykład

Odpowiedzi:

355

Aby to zadziałało, musisz przekazać $ silnia jako odniesienie

$factorial = function( $n ) use ( &$factorial ) {
    if( $n == 1 ) return 1;
    return $factorial( $n - 1 ) * $n;
};
print $factorial( 5 );
Derek H.
źródło
to dziwne obiekty bc powinny być zawsze przekazywane przez referencję i anon. funkcje są obiektami ...
ellabeauty
25
@ellabeauty w czasie, gdy $ silnia jest przekazywana, wciąż jest zerowa (nie zdefiniowana), dlatego musisz przekazać ją przez referencję. Pamiętaj, że jeśli zmodyfikujesz silnię $ przed wywołaniem funkcji, wynik zmieni się, gdy zostanie przekazany przez referencję.
Marius Balčytis
9
@ellabeauty: Nie, całkowicie źle to rozumiesz. Wszystko bez &jest wartościowe. Wszystko z tym &jest przez odniesienie. „Obiekty” nie są wartościami w PHP5 i nie można ich przypisywać ani przekazywać. Masz do czynienia ze zmienną, której wartość jest odwołaniem do obiektu. Jak wszystkie zmienne, może być przechwycony przez wartość lub przez odniesienie, w zależności od tego, czy istnieje &.
newacct
3
Mind Blown! Wielkie dzięki! Jak do tej pory o tym nie wiedziałem? Ilość aplikacji, jaką mam na rekurencyjne funkcje anonimowe, jest ogromna. Teraz mogę w końcu zapętlać zagnieżdżone struktury w układach bez konieczności jawnego definiowania metody i trzymania wszystkich danych układu poza moimi klasami.
Dieter Gribnitz
Jak powiedział @barius, zachowaj ostrożność podczas używania go w foreach. $factorialzostaną zmienione przed wywołaniem funkcji i może to spowodować dziwne zachowanie.
stil
24

Wiem, że to może nie być proste podejście, ale dowiedziałem się o technice zwanej „poprawką” z języków funkcjonalnych. fixFunkcję z Haskell jest znany ogólnie jako combinator Y , który jest jednym z najbardziej znanych złożone jest ze stałą .

Punkt stały jest wartością niezmienioną przez funkcję: punktem stałym funkcji f jest dowolny x taki, że x = f (x). Kombinator punktu stałego y jest funkcją, która zwraca punkt stały dla dowolnej funkcji f. Ponieważ y (f) jest stałym punktem f, mamy y (f) = f (y (f)).

Zasadniczo kombinator Y tworzy nową funkcję, która przyjmuje wszystkie argumenty oryginału, plus dodatkowy argument, który jest funkcją rekurencyjną. Jak to działa, jest bardziej oczywiste, używając notacji curry. Zamiast pisać argumenty w nawiasach ( f(x,y,...)), napisać je po funkcji: f x y .... Kombinator Y jest zdefiniowany jako Y f = f (Y f); lub z jednego argumentu funkcji recursed, Y f x = f (Y f) x.

Ponieważ PHP nie działa automatycznie curry funkcji, jest trochę hack do fixpracy, ale myślę, że to interesujące.

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

Pamiętaj, że jest to prawie to samo, co proste rozwiązania zamykające, które opublikowali inni, ale funkcja fixtworzy zamknięcie dla Ciebie. Kombinatory stałoprzecinkowe są nieco bardziej złożone niż użycie zamknięcia, ale są bardziej ogólne i mają inne zastosowania. Chociaż metoda zamknięcia jest bardziej odpowiednia dla PHP (który nie jest strasznie funkcjonalnym językiem), pierwotny problem jest bardziej ćwiczeniem niż produkcją, więc kombinator Y jest realnym podejściem.

Kendall Hopkins
źródło
10
Warto zauważyć, że call_user_func_array()jest wolny jak Boże Narodzenie.
Xeoncross,
11
@Xeoncross W przeciwieństwie do reszty PHP, która ustanawia rekord prędkości lądowej? : P
Kendall Hopkins,
1
Uwaga: możesz teraz (5.6+) użyć rozpakowywania argumentów zamiast call_user_func_array.
Fabien Sa
@KendallHopkins dlaczego te dodatkowe argumenty array_unshift( $args, fix($func) );? Argumenty są już obciążone parametrami, a faktyczna rekurencja jest wykonywana przez call_user_func_array (), więc co robi ta linia?
Chcę odpowiedzi
5

Chociaż nie jest to do użytku praktycznego, rozszerzenie mpyw-junks / phpext-callee na poziomie C zapewnia anonimową rekurencję bez przypisywania zmiennych .

<?php

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

// 5! = 5 * 4 * 3 * 2 * 1 = int(120)
mpyw
źródło
0

W nowszych wersjach PHP możesz to zrobić:

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

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

Może to potencjalnie prowadzić do dziwnych zachowań.

jgmjgm
źródło
0

Możesz używać Y Combinator w PHP 7.1+ jak poniżej:

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

Graj z nim: https://3v4l.org/7AUn2

Kody źródłowe z: https://github.com/whitephp/the-little-phper/blob/master/src/chapter_9.php

Lei Fan
źródło
0

Z anonimową klasą (PHP 7+), bez definiowania zmiennej:

echo (new class {
    function __invoke($n) {
        return $n < 2 ? 1 : $n * $this($n - 1);
    }
})(5);
Ayell
źródło