Anonim özyinelemeli PHP işlevleri


198

Hem özyinelemeli hem de anonim bir PHP fonksiyonuna sahip olmak mümkün mü? Bu benim işe alma girişimim, ama işlev adını geçmiyor.

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

Bunun faktöriyeli uygulamanın kötü bir yolu olduğunun da farkındayım, sadece bir örnek.


Kontrol etmek için PHP 5.3.0'ım yok ama kullanmayı denedin global $factorialmi?
kennytm

5
(sidenote) bir Lamba anonim bir işlevken, yukarıdaki bir Kapanıştır.
Gordon

1
Lambdas ve Kapaklar birbirini dışlamaz. Aslında bazı insanlar, bir kapatma (anonim işlev) olabilmesi için bir kapanmanın lambda olması gerektiğine inanırlar. Örneğin, tür Python, işleve önce bir ad vermek zorundadır (sürüme bağlı olarak). Çünkü ona satılamayacağınız bir isim vermek zorundasınız ve bazıları bunu bir kapatma olmaktan çıkarır.
Adam Gent

1
print $factorial( 0);
nickb

php Manuel örnek

Yanıtlar:


357

Çalışması için $ factorial'ı referans olarak geçmeniz gerekir

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

bu garip bc nesneleri her zaman referans ve anon tarafından geçirilmelidir. fonksiyonları nesnelerdir ...
ellabeauty

25
$ faktörünün geçtiği zaman @ellabeauty hala boş (tanımlanmamış), bu yüzden referans ile geçmeniz gerekiyor. İşlevi çağırmadan önce $ faktörünü değiştirirseniz, sonucun referans olarak iletildikçe değişeceğini unutmayın.
Marius Balčytis

9
@ellabeauty: Hayır, tamamen yanlış anlıyorsun. Olmadan her şey &değerdir. İle her şey &referans içindir. "Nesneler" PHP5'teki değerler değildir ve atanamaz veya iletilemez. Değeri bir nesne başvurusu olan bir değişkenle uğraşıyorsunuz. Tüm değişkenler gibi, bir değer bulunup bulunmadığına bağlı olarak değere veya referansa göre yakalanabilir &.
newacct

3
Zihin karmaşası! Çok teşekkürler! Bunu şimdiye kadar nasıl bilmiyordum? Özyinelemeli anonim işlevler için sahip olduğum uygulama miktarı çok büyük. Şimdi nihayet mizanpajlardaki iç içe yapılar arasında açıkça bir yöntem tanımlamak ve tüm düzen verilerimi sınıflarımın dışında tutmak zorunda kalmadan dönebilirim.
Dieter Gribnitz

@Barius'un dediği gibi, foreach içinde kullanırken dikkatli olun. $factorialişlev çağrılmadan önce değiştirilir ve garip davranışlarla sonuçlanabilir.
stil

24

Bunun basit bir yaklaşım olmayabileceğini biliyorum, ama fonksiyonel dillerden "düzeltme" adı verilen bir teknik öğrendim . fixHaskell fonksiyonu olarak daha genel olarak bilinen Y combinator en iyi bilinen biri, sabit nokta bağdaştırıcılarla .

Sabit nokta, bir işlev tarafından değiştirilmeyen bir değerdir: f işlevinin sabit noktası, x = f (x) olacak şekilde herhangi bir x'dir . Sabit nokta birleştiricisi y , herhangi bir işlev f için sabit nokta döndüren bir işlevdir. Y (f) sabit bir f noktası olduğundan, y (f) = f (y (f)) değerine sahibiz.

Esasen, Y birleştiricisi, orijinalin tüm argümanlarını alan yeni bir işlev ve ayrıca özyinelemeli işlev olan ek bir argüman oluşturur. Bunun nasıl çalıştığı, kavisli gösterimi kullanarak daha açıktır. Bunun yerine parantez içinde argüman yazma ( f(x,y,...)), fonksiyonu sonra bunları yazın: f x y .... Y birleştiricisi Y f = f (Y f); veya yinelenen işlev için tek bir argümanla Y f x = f (Y f) x.

PHP işlevleri otomatik olarak köri yapmadığından , fixiş yapmak için bir kesmek biraz , ama ilginç olduğunu düşünüyorum.

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

Bunun, başkalarının gönderdiği basit kapatma çözümleriyle hemen hemen aynı olduğunu unutmayın, ancak işlev fixsizin için kapağı oluşturur. Sabit nokta birleştiricileri, bir kapak kullanmaktan biraz daha karmaşıktır, ancak daha geneldir ve başka kullanımları vardır. Kapatma yöntemi PHP için (çok fonksiyonel bir dil değildir) daha uygun olsa da, asıl sorun üretimden çok bir alıştırmadır, bu nedenle Y birleştiricisi uygulanabilir bir yaklaşımdır.


10
call_user_func_array()Noel kadar yavaş olduğunu belirtmek gerekir .
Xeoncross

11
@Xeoncross PHP'nin geri kalanının aksine, kara hızı rekorunu kırıyor mu? : P
Kendall Hopkins

1
Not, artık (5.6+) argümanını açmak yerine kullanabilirsiniz call_user_func_array.
Fabien Sa

@KendallHopkins neden bu ek argümanlar array_unshift( $args, fix($func) );? Args parametrelerle zaten yüklüdür ve gerçek özyineleme call_user_func_array () tarafından yapılır, bu nedenle bu satır ne işe yarar?
Cevaplar İstiyorum

5

Pratik kullanım için olmasa da, C düzeyi uzantısı mpyw-junks / phpext-callee , değişken atamadan anonim özyineleme sağlar .

<?php

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

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

0

PHP'nin yeni sürümlerinde bunu yapabilirsiniz:

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

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

Bu potansiyel olarak garip davranışlara yol açabilir.


0

PHP 7.1+ sürümünde Y Combinator'ı aşağıdaki gibi kullanabilirsiniz:

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

Onunla oyna: https://3v4l.org/7AUn2

Kaynak kodları: https://github.com/whitephp/the-little-phper/blob/master/src/chapter_9.php


0

Anonim bir sınıfla (PHP 7+), bir değişken tanımlamaksızın:

echo (new class {
    function __invoke($n) {
        return $n < 2 ? 1 : $n * $this($n - 1);
    }
})(5);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.