Apa artinya menghasilkan dalam PHP?

232

Saya baru-baru ini menemukan kode ini:

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

Saya belum pernah melihat ini yield kata kunci sebelumnya. Mencoba menjalankan kode yang saya dapatkan

Kesalahan Parse: kesalahan sintaks, T_VARIABLE yang tidak terduga pada baris x

Jadi apa yieldkata kunci ini ? Apakah ini PHP yang valid? Dan jika ya, bagaimana cara menggunakannya?

Gordon
sumber

Jawaban:

355

Apa yield?

Kata yieldkunci mengembalikan data dari fungsi generator:

Jantung dari fungsi generator adalah kata kunci hasil. Dalam bentuknya yang paling sederhana, pernyataan hasil lebih terlihat seperti pernyataan pengembalian, kecuali bahwa alih-alih menghentikan eksekusi fungsi dan kembali, hasil sebaliknya memberikan nilai pada perulangan kode di atas generator dan menghentikan sementara eksekusi fungsi generator.

Apa itu fungsi generator?

Fungsi generator secara efektif merupakan cara yang lebih ringkas dan efisien untuk menulis Iterator . Ini memungkinkan Anda untuk mendefinisikan fungsi (Anda xrange) yang akan menghitung dan mengembalikan nilai saat Anda mengulanginya :

foreach (xrange(1, 10) as $key => $value) {
    echo "$key => $value", PHP_EOL;
}

Ini akan menghasilkan output sebagai berikut:

0 => 1
1 => 2

9 => 10

Anda juga dapat mengontrol $keydalam foreachdengan menggunakan

yield $someKey => $someValue;

Dalam fungsi generator, $someKeyadalah apa pun yang Anda inginkan muncul $keydan $someValuemenjadi nilai $val. Dalam contoh pertanyaan itu$i .

Apa bedanya dengan fungsi normal?

Sekarang Anda mungkin bertanya-tanya mengapa kami tidak hanya menggunakan rangefungsi asli PHP untuk mencapai hasil itu. Dan Anda benar. Outputnya akan sama. Perbedaannya adalah bagaimana kita sampai di sana.

Ketika kita menggunakan rangePHP, akan melaksanakannya, membuat seluruh array angka dalam memori dan returnbahwa seluruh array ke foreachlingkaran yang kemudian akan pergi lebih dari itu dan output nilai-nilai. Dengan kata lain, foreachakan beroperasi pada array itu sendiri. The rangeFungsi dan foreachhanya "bicara" sekali. Anggap saja seperti mendapatkan paket melalui pos. Si kurir akan menyerahkan paket itu kepada Anda dan pergi. Dan kemudian Anda membuka bungkus seluruh paket, mengambil apa pun yang ada di sana.

Ketika kita menggunakan fungsi generator, PHP akan masuk ke fungsi dan menjalankannya sampai memenuhi akhir atau yieldkata kunci. Ketika bertemu a yield, ia kemudian akan mengembalikan nilai apa pun pada saat itu ke loop luar. Kemudian ia kembali ke fungsi generator dan berlanjut dari tempat ia menghasilkan. Karena Anda xrangememegang forloop, itu akan mengeksekusi dan menghasilkan sampai $maxtercapai. Anggap saja seperti foreachdan generator bermain ping pong.

Mengapa saya membutuhkannya?

Jelas, generator dapat digunakan untuk bekerja di sekitar batas memori. Tergantung pada lingkungan Anda, melakukan range(1, 1000000)wasiat akan mematikan skrip Anda sedangkan yang sama dengan generator hanya akan berfungsi dengan baik. Atau seperti yang Wikipedia katakan:

Karena generator menghitung nilai yang dihasilkan hanya berdasarkan permintaan, mereka berguna untuk mewakili urutan yang akan mahal atau tidak mungkin untuk dihitung sekaligus. Ini termasuk misalnya urutan tak terbatas dan aliran data langsung.

Generator juga seharusnya cukup cepat. Tetapi perlu diingat bahwa ketika kita berbicara tentang puasa, kita biasanya berbicara dalam jumlah yang sangat kecil. Jadi sebelum Anda sekarang lari dan mengubah semua kode Anda untuk menggunakan generator, lakukan benchmark untuk melihat di mana itu masuk akal.

Case Penggunaan lain untuk Generator adalah coroutine asinkron. Kata yieldkunci tidak hanya mengembalikan nilai tetapi juga menerimanya. Untuk detailnya, lihat dua posting blog bagus yang ditautkan di bawah ini.

Sejak kapan saya bisa menggunakan yield?

Generator telah diperkenalkan di PHP 5.5 . Mencoba menggunakan yieldsebelum versi itu akan menghasilkan berbagai kesalahan parse, tergantung pada kode yang mengikuti kata kunci. Jadi, jika Anda mendapatkan kesalahan parse dari kode itu, perbarui PHP Anda.

Sumber dan bacaan lebih lanjut:

Gordon
sumber
1
Tolong jelaskan apa manfaatnya yeild, katakanlah, solusi seperti ini: ideone.com/xgqevM
Mike
1
Ah, well, dan pemberitahuan yang saya hasilkan. Hah. Yah, saya bereksperimen dalam meniru Generator untuk PHP> = 5.0.0 dengan kelas helper, dan yeah, sedikit kurang dapat dibaca, tetapi saya dapat menggunakan ini di masa depan. Topik menarik. Terima kasih!
Mike
Bukan keterbacaan tetapi penggunaan memori! Bandingkan memori yang digunakan untuk mengulangi return range(1,100000000)dan for ($i=0; $i<100000000; $i++) yield $i
emix
@ Mike ya, itu sudah dijelaskan dalam jawaban saya. Dalam contoh Mike yang lain, memori hampir tidak menjadi masalah karena dia hanya mengulangi 10 nilai.
Gordon
1
@ Mike Salah satu masalah dengan xrange adalah bahwa penggunaan batas statika adalah kegunaan untuk bersarang, misalnya (misalnya mencari manifold n dimensional, atau quicksort rekursif menggunakan generator, misalnya). Anda tidak dapat membuat sarang xrange loop karena hanya ada satu contoh dari penghitungnya. Versi Yield tidak mengalami masalah ini.
Shayne
43

Fungsi ini menggunakan hasil:

function a($items) {
    foreach ($items as $item) {
        yield $item + 1;
    }
}

hampir sama dengan yang ini tanpa:

function b($items) {
    $result = [];
    foreach ($items as $item) {
        $result[] = $item + 1;
    }
    return $result;
}

Satu-satunya perbedaan adalah yang a()mengembalikan generator danb() array sederhana. Anda dapat mengulangi keduanya.

Juga, yang pertama tidak mengalokasikan array penuh dan karenanya kurang menuntut memori.

tsusanka
sumber
2
tambahkan catatan dari dokumen resmi: Di ​​PHP 5, generator tidak dapat mengembalikan nilai: melakukan hal itu akan menghasilkan kesalahan kompilasi. Pernyataan pengembalian kosong adalah sintaks yang valid di dalam generator dan itu akan menghentikan generator. Sejak PHP 7.0, generator dapat mengembalikan nilai, yang dapat diambil menggunakan Generator :: getReturn (). php.net/manual/en/language.generators.syntax.php
Programmer Dancuk
Sederhana dan ringkas.
John Miller
24

contoh sederhana

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $v)
    echo $v.',';
echo '#end main#';
?>

keluaran

#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#

contoh lanjutan

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $k => $v){
    if($k === 5)
        break;
    echo $k.'=>'.$v.',';
}
echo '#end main#';
?>

keluaran

#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#
Berpikir Besar
sumber
Jadi, ia kembali tanpa mengganggu fungsinya?
Lucas Bustamante
22

yieldkata kunci berfungsi untuk definisi "generator" di PHP 5.5. Ok, lalu apa itu generator ?

Dari php.net:

Generator menyediakan cara mudah untuk mengimplementasikan iterator sederhana tanpa overhead atau kompleksitas mengimplementasikan kelas yang mengimplementasikan antarmuka Iterator.

Sebuah generator memungkinkan Anda untuk menulis kode yang menggunakan masing-masing untuk beralih pada satu set data tanpa perlu membangun sebuah array dalam memori, yang dapat menyebabkan Anda melebihi batas memori, atau membutuhkan banyak waktu pemrosesan untuk menghasilkan. Sebagai gantinya, Anda dapat menulis fungsi generator, yang sama dengan fungsi normal, kecuali bahwa alih-alih kembali satu kali, generator dapat menghasilkan sebanyak yang diperlukan untuk memberikan nilai yang akan diulangi.

Dari tempat ini: generator = generator, fungsi lain (hanya fungsi sederhana) = fungsi.

Jadi, mereka berguna ketika:

  • Anda perlu melakukan hal-hal sederhana (atau hal-hal sederhana);

    Generator benar-benar jauh lebih sederhana daripada mengimplementasikan antarmuka Iterator. Sebaliknya, sumber daya, bahwa generator kurang berfungsi. bandingkan mereka .

  • Anda perlu menghasilkan sejumlah besar data - menghemat memori;

    sebenarnya untuk menghemat memori kita hanya bisa menghasilkan data yang dibutuhkan melalui fungsi untuk setiap iterasi loop, dan setelah iterasi menggunakan sampah. jadi di sini poin utama adalah - kode yang jelas dan mungkin kinerja. lihat apa yang lebih baik untuk kebutuhan Anda.

  • Anda perlu membuat urutan, yang tergantung pada nilai perantara;

    ini meluas dari pemikiran sebelumnya. generator dapat membuat segalanya lebih mudah dibandingkan dengan fungsi. periksa contoh Fibonacci , dan cobalah membuat urutan tanpa generator. Generator juga dapat bekerja lebih cepat dalam kasus ini, setidaknya karena menyimpan nilai perantara dalam variabel lokal;

  • Anda perlu meningkatkan kinerja.

    mereka dapat bekerja lebih cepat daripada berfungsi dalam beberapa kasus (lihat manfaat sebelumnya);

QArea
sumber
1
Saya tidak mengerti bagaimana generator bekerja. kelas ini mengimplementasikan antarmuka iterator. dari apa yang saya ketahui kelas iterators memungkinkan saya untuk mengonfigurasi bagaimana saya ingin mengulangi suatu objek. misalnya ArrayIterator mendapatkan array atau objek sehingga saya bisa memodifikasi nilai dan kunci saat iterasi. jadi jika iterators mendapatkan seluruh objek / array maka bagaimana generator tidak harus membangun seluruh array dalam memori ???
user3021621
7

Dengan yieldAnda dapat dengan mudah menggambarkan breakpoints antara banyak tugas dalam satu fungsi. Itu saja, tidak ada yang istimewa tentang itu.

$closure = function ($injected1, $injected2, ...){
    $returned = array();
    //task1 on $injected1
    $returned[] = $returned1;
//I need a breakpoint here!!!!!!!!!!!!!!!!!!!!!!!!!
    //task2 on $injected2
    $returned[] = $returned2;
    //...
    return $returned;
};
$returned = $closure($injected1, $injected2, ...);

Jika task1 dan task2 sangat terkait, tetapi Anda perlu breakpoint di antara mereka untuk melakukan sesuatu yang lain:

  • membebaskan memori di antara pemrosesan baris database
  • menjalankan tugas lain yang memberikan ketergantungan pada tugas berikutnya, tetapi yang tidak terkait dengan memahami kode saat ini
  • melakukan panggilan async dan menunggu hasilnya
  • dan seterusnya ...

maka generator adalah solusi terbaik, karena Anda tidak perlu membagi kode Anda menjadi banyak penutupan atau mencampurnya dengan kode lain, atau menggunakan panggilan balik, dll ... Anda hanya yieldperlu menambahkan breakpoint, dan Anda dapat melanjutkan dari itu breakpoint jika Anda siap.

Tambahkan breakpoint tanpa generator:

$closure1 = function ($injected1){
    //task1 on $injected1
    return $returned1;
};
$closure2 = function ($injected2){
    //task2 on $injected2
    return $returned1;
};
//...
$returned1 = $closure1($injected1);
//breakpoint between task1 and task2
$returned2 = $closure2($injected2);
//...

Tambahkan breakpoint dengan generator

$closure = function (){
    $injected1 = yield;
    //task1 on $injected1
    $injected2 = (yield($returned1));
    //task2 on $injected2
    $injected3 = (yield($returned2));
    //...
    yield($returnedN);
};
$generator = $closure();
$returned1 = $generator->send($injected1);
//breakpoint between task1 and task2
$returned2 = $generator->send($injected2);
//...
$returnedN = $generator->send($injectedN);

Catatan: Mudah membuat kesalahan dengan generator, jadi selalu tulis unit test sebelum Anda menerapkannya! note2: Menggunakan generator dalam loop tak terbatas seperti menulis penutupan yang memiliki panjang tak terbatas ...

inf3rno
sumber
4

Tidak ada jawaban di atas yang menunjukkan contoh nyata menggunakan array besar yang dihuni oleh anggota non-numerik. Berikut adalah contoh menggunakan array yang dihasilkan oleh explode()pada file .txt besar (262MB dalam kasus penggunaan saya):

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

$path = './file.txt';
$content = file_get_contents($path);

foreach(explode("\n", $content) as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

Outputnya adalah:

Starting memory usage: 415160
Final memory usage: 270948256

Sekarang bandingkan dengan skrip yang serupa, menggunakan yieldkata kunci:

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

function x() {
    $path = './file.txt';
    $content = file_get_contents($path);
    foreach(explode("\n", $content) as $x) {
        yield $x;
    }
}

foreach(x() as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

Output untuk skrip ini adalah:

Starting memory usage: 415152
Final memory usage: 415616

Jelas penghematan penggunaan memori sangat besar (emMemoryUsage -----> ~ 270,5 MB pada contoh pertama, ~ 450B pada contoh kedua).

David Partyka
sumber
3

Aspek yang menarik, yang layak untuk dibahas di sini, adalah menghasilkan dengan referensi . Setiap kali kita perlu mengubah parameter sedemikian rupa sehingga tercermin di luar fungsi, kita harus melewatkan parameter ini dengan referensi. Untuk menerapkan ini pada generator, kami cukup menambahkan ampersand &ke nama generator dan variabel yang digunakan dalam iterasi:

 <?php 
 /**
 * Yields by reference.
 * @param int $from
 */
function &counter($from) {
    while ($from > 0) {
        yield $from;
    }
}

foreach (counter(100) as &$value) {
    $value--;
    echo $value . '...';
}

// Output: 99...98...97...96...95...

Contoh di atas menunjukkan bagaimana mengubah nilai yang foreachdiulang dalam loop mengubah $fromvariabel dalam generator. Hal ini karena $fromini dihasilkan dengan mengacu karena ampersand sebelum nama generator. Karena itu, $valuevariabel dalam foreachloop adalah referensi ke $fromvariabel dalam fungsi generator.

Bud Damyanov
sumber
0

Kode di bawah ini mengilustrasikan bagaimana menggunakan generator mengembalikan hasil sebelum selesai, tidak seperti pendekatan non generator tradisional yang mengembalikan array lengkap setelah iterasi penuh. Dengan generator di bawah ini, nilai-nilai dikembalikan ketika siap, tidak perlu menunggu array diisi sepenuhnya:

<?php 

function sleepiterate($length) {
    for ($i=0; $i < $length; $i++) {
        sleep(2);
        yield $i;
    }
}

foreach (sleepiterate(5) as $i) {
    echo $i, PHP_EOL;
}
Risteard
sumber
Jadi, tidak mungkin menggunakan yield untuk menghasilkan kode html di php? Saya tidak tahu manfaat di lingkungan nyata
Giuseppe Lodi Rizzini
@GiuseppeLodiRizzini apa yang membuat Anda berpikir begitu?
Brad Kent