Fungsi PHP rekursif anonim

197

Apakah mungkin untuk memiliki fungsi PHP yang bersifat rekursif dan anonim? Ini adalah upaya saya untuk membuatnya berfungsi, tetapi tidak lulus dalam nama fungsi.

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

Saya juga sadar bahwa ini adalah cara yang buruk untuk menerapkan faktorial, itu hanya sebuah contoh.

Kendall Hopkins
sumber
Saya tidak memiliki PHP 5.3.0 untuk memeriksa tetapi apakah Anda mencoba menggunakan global $factorial?
kennytm
5
(sidenote) Lamba adalah fungsi anonim, sedangkan di atas adalah Penutupan.
Gordon
1
Lambdas dan Penutupan tidak saling eksklusif. Bahkan beberapa orang percaya bahwa penutupan harus lambda untuk itu menjadi penutupan (fungsi anonim). Sebagai contoh Python Anda semacam harus memberi fungsi nama terlebih dahulu (tergantung pada versi). Karena Anda harus memberikannya nama yang tidak dapat Anda sebaris dan beberapa akan mengatakan itu mendiskualifikasi dari menjadi penutupan.
Adam Gent
1
print $factorial( 0);
nickb

Jawaban:

356

Agar itu berfungsi, Anda harus memberikan $ factorial sebagai referensi

$factorial = function( $n ) use ( &$factorial ) {
    if( $n == 1 ) return 1;
    return $factorial( $n - 1 ) * $n;
};
print $factorial( 5 );
Derek H
sumber
itu benda aneh bc harus selalu dilewatkan dengan referensi, dan langsung. fungsi adalah objek ...
ellabeauty
25
@ellabeauty dalam waktu $ faktorial berlalu, itu masih nol (tidak didefinisikan), itu sebabnya Anda harus melewatinya dengan referensi. Perlu diketahui, bahwa jika Anda memodifikasi $ faktorial sebelum memanggil fungsi, hasilnya akan berubah saat dilewatkan oleh referensi.
Marius Balčytis
9
@ellabeauty: Tidak, Anda salah paham. Semuanya tanpa &nilai. Semuanya dengan &referensi. "Objek" bukan nilai dalam PHP5 dan tidak dapat ditetapkan atau diteruskan. Anda berhadapan dengan variabel yang nilainya adalah referensi objek. Seperti semua variabel, ini dapat ditangkap dengan nilai atau referensi, tergantung pada apakah ada &.
newacct
3
Mind Blown! Terima kasih banyak! Bagaimana saya tidak tahu tentang ini sampai sekarang? Jumlah aplikasi yang saya miliki untuk fungsi anonim rekursif sangat besar. Sekarang saya akhirnya bisa mengulang melalui struktur bersarang dalam tata letak tanpa harus secara eksplisit mendefinisikan metode dan menjaga semua data tata letak saya keluar dari kelas saya.
Dieter Gribnitz
Seperti kata @barius, berhati-hatilah saat menggunakannya di muka. $factorialakan diubah sebelum fungsi dipanggil dan itu mungkin terjadi dengan perilaku aneh.
stil
24

Saya tahu ini mungkin bukan pendekatan yang sederhana, tetapi saya belajar tentang teknik yang disebut "memperbaiki" dari bahasa fungsional. The fixFungsi dari Haskell dikenal lebih umum sebagai combinator Y , yang merupakan salah satu yang paling terkenal combinators titik tetap .

Titik tetap adalah nilai yang tidak diubah oleh suatu fungsi: titik tetap dari fungsi f adalah x sehingga x = f (x). Sebuah titik tetap combinator y adalah fungsi yang mengembalikan titik tetap untuk setiap fungsi f. Karena y (f) adalah titik tetap dari f, kita memiliki y (f) = f (y (f)).

Pada dasarnya, kombinator Y menciptakan fungsi baru yang mengambil semua argumen dari yang asli, ditambah argumen tambahan yang merupakan fungsi rekursif. Cara kerjanya lebih jelas menggunakan notasi curried. Alih-alih menulis argumen dalam kurung ( f(x,y,...)), menulis mereka setelah fungsi: f x y .... Combinator Y didefinisikan sebagai Y f = f (Y f); atau, dengan argumen tunggal untuk fungsi berulang Y f x = f (Y f) x,.

Karena PHP tidak secara otomatis menjelajah fungsi, ini sedikit hack untuk membuat fixpekerjaan, tapi saya pikir ini menarik.

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

Perhatikan bahwa ini hampir sama dengan solusi penutupan sederhana yang diposkan orang lain, tetapi fungsinya fixmenciptakan penutupan untuk Anda. Combinator titik tetap sedikit lebih kompleks daripada menggunakan penutup, tetapi lebih umum, dan memiliki kegunaan lain. Sementara metode penutupan lebih cocok untuk PHP (yang bukan bahasa yang sangat fungsional), masalah asli lebih merupakan latihan daripada untuk produksi, sehingga kombinator Y adalah pendekatan yang layak.

Kendall Hopkins
sumber
10
Perlu dicatat bahwa call_user_func_array()ini lambat seperti Natal.
Xeoncross
11
@ Xeoncross Berbeda dengan sisa PHP yang menetapkan rekor kecepatan darat? : P
Kendall Hopkins
1
Catatan, Anda sekarang dapat (5.6+) menggunakan argumen membongkar bukan call_user_func_array.
Fabien Sa
@KallallHopkins mengapa ini argumen tambahan array_unshift( $args, fix($func) );? Args sudah sarat dengan parameter, dan rekursi aktual dilakukan oleh call_user_func_array (), jadi apa yang dilakukan baris itu?
I Want Answers
5

Meskipun bukan untuk penggunaan praktis, ekstensi tingkat-C mpyw-junk / phpext-callee menyediakan rekursi anonim tanpa menetapkan variabel .

<?php

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

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

Di versi PHP yang lebih baru Anda dapat melakukan ini:

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

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

Ini berpotensi menyebabkan perilaku aneh.

jgmjgm
sumber
0

Anda dapat menggunakan Y Combinator di PHP 7.1+ seperti di bawah ini:

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

Mainkan dengannya: https://3v4l.org/7AUn2

Kode sumber dari: https://github.com/whitephp/the-little-phper/blob/master/src/chapter_9.php

Lei Fan
sumber
0

Dengan kelas anonim (PHP 7+), tanpa mendefinisikan variabel:

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