PHP มีความหมายอย่างไร


232

ฉันเพิ่งพบรหัสนี้:

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

ฉันไม่เคยเห็นyieldคำหลักนี้มาก่อน พยายามเรียกใช้รหัสที่ฉันได้รับ

ข้อผิดพลาดในการแยกวิเคราะห์: ข้อผิดพลาดทางไวยากรณ์ T_VARIABLE ที่ไม่คาดคิดบนบรรทัด x

yieldคำหลักนี้คืออะไร? มันยังใช้งานได้กับ PHP? และถ้าเป็นฉันจะใช้มันได้อย่างไร

คำตอบ:


355

คือyieldอะไร

yieldคำหลักข้อมูลผลตอบแทนจากการทำงานเครื่องกำเนิดไฟฟ้า:

หัวใจของฟังก์ชั่นเครื่องกำเนิดไฟฟ้าคือคีย์เวิร์ดผลผลิต ในรูปแบบที่ง่ายที่สุดข้อความสั่ง return ดูเหมือนกับคำสั่ง return ยกเว้นว่าแทนที่จะหยุดการทำงานของฟังก์ชั่นและการกลับมาแทนจะให้ค่าแทนการวนรอบโค้ดเหนือตัวสร้างและหยุดการทำงานของตัวสร้าง

ฟังก์ชั่นเครื่องกำเนิดไฟฟ้าคืออะไร?

ฟังก์ชั่นเครื่องกำเนิดไฟฟ้าได้อย่างมีประสิทธิภาพเป็นวิธีที่มีขนาดกะทัดรัดและมีประสิทธิภาพในการเขียนIterator ช่วยให้คุณกำหนดฟังก์ชั่น (ของคุณxrange) ที่จะคำนวณและส่งกลับค่าในขณะที่คุณวนลูปมัน :

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

สิ่งนี้จะสร้างผลลัพธ์ต่อไปนี้:

0 => 1
1 => 2

9 => 10

นอกจากนี้คุณยังสามารถควบคุมการ$keyในforeachโดยใช้

yield $someKey => $someValue;

ในการทำงานของเครื่องกำเนิดไฟฟ้า$someKeyเป็นสิ่งที่คุณต้องการสำหรับการปรากฏ$keyและเป็นค่าในการ$someValue $valในตัวอย่างของคำถามนั่นคือ$iในตัวอย่างคำถามที่ว่า

ฟังก์ชั่นปกติต่างกันอย่างไร

ตอนนี้คุณอาจสงสัยว่าทำไมเราไม่เพียงแค่ใช้ฟังก์ชั่นพื้นฐานของ PHPrangeเพื่อให้ได้ผลลัพธ์นั้น และถูกต้องคุณ ผลลัพธ์จะเหมือนกัน ความแตกต่างคือวิธีที่เราไปถึงที่นั่น

เมื่อเราใช้rangePHP จะดำเนินการสร้างอาร์เรย์ทั้งหมดของตัวเลขในหน่วยความจำและอาร์เรย์returnนั้นทั้งหมดไปยังforeachลูปซึ่งจะผ่านมันและส่งออกค่า กล่าวอีกนัยหนึ่งforeachจะทำงานกับอาเรย์เอง rangeฟังก์ชั่นและforeachเพียง "พูดคุย" ครั้งเดียว คิดว่ามันเหมือนได้รับแพคเกจในจดหมาย คนส่งมอบจะมอบแพ็คเกจให้คุณ และจากนั้นคุณก็แกะห่อบรรจุภัณฑ์ทั้งหมดโดยกำจัดสิ่งที่อยู่ในนั้นออก

เมื่อเราใช้ฟังก์ชั่นเครื่องกำเนิดไฟฟ้า PHP จะก้าวเข้าสู่ฟังก์ชั่นและดำเนินการจนกว่าจะเป็นไปตามจุดสิ้นสุดหรือyieldคำหลัก เมื่อตรงกับ a yieldมันจะคืนค่าอะไรก็ตามในเวลานั้นไปยังลูปด้านนอก จากนั้นมันจะกลับไปที่ฟังก์ชันตัวสร้างและดำเนินการต่อจากจุดที่มันให้ เมื่อคุณxrangeถือforลูปมันจะดำเนินการและให้ผลจนกว่าจะ$maxถึง คิดว่ามันเหมือนforeachและเครื่องกำเนิดไฟฟ้าเล่นปิงปอง

ทำไมฉันต้องการมัน

เห็นได้ชัดว่าเครื่องกำเนิดไฟฟ้าสามารถใช้แก้ไขข้อ จำกัด หน่วยความจำ ขึ้นอยู่กับสภาพแวดล้อมของคุณการทำrange(1, 1000000)จะทำให้สคริปต์ของคุณเสียชีวิตในขณะที่ตัวกำเนิดเดียวกันจะทำงานได้ดี หรือตามที่ Wikipedia วางไว้:

เนื่องจากเครื่องกำเนิดไฟฟ้าคำนวณค่าที่ได้รับตามความต้องการเท่านั้นจึงมีประโยชน์สำหรับการแสดงลำดับที่อาจมีราคาแพงหรือไม่สามารถคำนวณได้ในครั้งเดียว เหล่านี้รวมถึงลำดับที่ไม่สิ้นสุดและสตรีมข้อมูลสด

เครื่องกำเนิดไฟฟ้าก็ควรจะค่อนข้างเร็ว แต่โปรดจำไว้ว่าเมื่อเราพูดถึงเรื่องรวดเร็วเรามักจะพูดกันในจำนวนที่น้อยมาก ดังนั้นก่อนที่คุณจะวิ่งออกไปและเปลี่ยนรหัสทั้งหมดของคุณเพื่อใช้เครื่องกำเนิดไฟฟ้าให้ทำเกณฑ์มาตรฐานเพื่อดูว่ามันเหมาะสมหรือไม่

กรณีการใช้งานอื่นสำหรับเครื่องกำเนิดไฟฟ้าคือ coroutines แบบอะซิงโครนัส yieldคำหลักไม่เพียง แต่ค่าตอบแทน แต่ก็ยังยอมรับพวกเขา สำหรับรายละเอียดเกี่ยวกับเรื่องนี้ดูโพสต์บล็อกยอดเยี่ยมทั้งสองลิงค์ด้านล่าง

ตั้งแต่เมื่อไหร่ที่ฉันจะใช้yield?

เครื่องกำเนิดไฟฟ้าได้รับการแนะนำในPHP 5.5 การพยายามใช้yieldก่อนหน้าเวอร์ชันนั้นจะส่งผลให้เกิดข้อผิดพลาดในการแยกวิเคราะห์ต่าง ๆ ขึ้นอยู่กับรหัสที่ตามหลังคำค้นหา ดังนั้นหากคุณได้รับข้อผิดพลาดในการแยกวิเคราะห์รหัสนั้นให้อัปเดต PHP ของคุณ

แหล่งที่มาและอ่านเพิ่มเติม:


1
โปรดอธิบายรายละเอียดเกี่ยวกับประโยชน์ของการyeildพูดจบการแก้ปัญหาเช่นนี้: ideone.com/xgqevM
Mike

1
อ่าและสิ่งที่ฉันกำลังสร้าง ฮะ. ฉันทดลองเลียนแบบเครื่องกำเนิดไฟฟ้าสำหรับ PHP> = 5.0.0 ด้วยผู้ช่วยระดับและใช่อ่านน้อยกว่าเล็กน้อย แต่ฉันอาจใช้สิ่งนี้ในอนาคต หัวข้อที่น่าสนใจ ขอบคุณ!
Mike

ไม่สามารถอ่านได้ แต่ใช้หน่วยความจำ! เปรียบเทียบหน่วยความจำที่ใช้สำหรับวนมากกว่าreturn range(1,100000000)และ for ($i=0; $i<100000000; $i++) yield $i
emix

@ ไมค์ใช่นั่นอธิบายไว้แล้วในคำตอบของฉัน ในหน่วยความจำตัวอย่างของไมค์อื่น ๆ แทบจะไม่เป็นปัญหาเพราะเขาทำซ้ำเพียง 10 ค่า
กอร์ดอน

1
@ ไมค์ปัญหาอย่างหนึ่งของ xrange ก็คือการใช้ข้อ จำกัด ทางสถิตินั้นมีประโยชน์สำหรับการซ้อนเช่น คุณไม่สามารถซ้อนลูป xrange ได้เนื่องจากมีเพียงอินสแตนซ์เดียวของตัวนับ เวอร์ชัน Yield ไม่ประสบปัญหานี้
เชน

43

ฟังก์ชั่นนี้ใช้อัตราผลตอบแทน:

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

เกือบจะเหมือนกับที่ไม่มี:

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

ข้อแตกต่างเดียวคือ a()คืนเครื่องกำเนิดไฟฟ้าและb()อาเรย์ง่ายๆ คุณสามารถทำซ้ำได้ทั้งสองอย่าง

นอกจากนี้อันแรกไม่ได้จัดสรรอาเรย์เต็มและดังนั้นจึงเรียกร้องความจำน้อย


2
หมายเหตุเพิ่มเติมจากเอกสารอย่างเป็นทางการ: ใน PHP 5 ตัวสร้างไม่สามารถส่งคืนค่าได้การทำเช่นนั้นจะส่งผลให้เกิดข้อผิดพลาดในการคอมไพล์ คำสั่งส่งคืนที่ว่างเปล่าเป็นไวยากรณ์ที่ถูกต้องภายในเครื่องกำเนิดไฟฟ้าและมันจะยุติเครื่องกำเนิด เนื่องจาก PHP 7.0 ตัวสร้างสามารถส่งคืนค่าซึ่งสามารถเรียกคืนได้โดยใช้ตัวสร้าง :: getReturn () php.net/manual/en/language.generators.syntax.php
โปรแกรมเมอร์ Dancuk

เรียบง่ายและรัดกุม
John Miller

24

ตัวอย่างง่ายๆ

<?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#';
?>

เอาท์พุต

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

ตัวอย่างขั้นสูง

<?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#';
?>

เอาท์พุต

#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#

ดังนั้นมันกลับมาโดยไม่ขัดจังหวะฟังก์ชั่น?
ลูคัสบัสตามันเต

22

yieldคำสำคัญทำหน้าที่สำหรับคำจำกัดความของ "กำเนิด" ใน PHP 5.5 ตกลงแล้วเครื่องกำเนิดคืออะไร?

จาก php.net:

เครื่องกำเนิดไฟฟ้าให้วิธีง่าย ๆ ในการใช้งานตัวทำซ้ำแบบง่ายโดยไม่มีค่าใช้จ่ายหรือความซับซ้อนของการใช้คลาสที่ใช้อินเทอร์เฟซ Iterator

เครื่องกำเนิดไฟฟ้าช่วยให้คุณสามารถเขียนโค้ดที่ใช้ foreach เพื่อวนซ้ำชุดข้อมูลโดยไม่จำเป็นต้องสร้างอาร์เรย์ในหน่วยความจำซึ่งอาจทำให้คุณเกินขีด จำกัด หน่วยความจำหรือต้องใช้เวลาในการประมวลผลจำนวนมาก คุณสามารถเขียนฟังก์ชั่นเครื่องกำเนิดไฟฟ้าซึ่งเหมือนกับฟังก์ชั่นปกติยกเว้นว่าจะส่งคืนหนึ่งครั้งเครื่องกำเนิดไฟฟ้าสามารถให้ผลลัพธ์ได้หลายครั้งตามที่ต้องการเพื่อให้ค่าที่จะทำซ้ำ

จากสถานที่นี้: กำเนิด = กำเนิด, ฟังก์ชั่นอื่น ๆ (เพียงฟังก์ชั่นที่เรียบง่าย) = ฟังก์ชั่น

ดังนั้นจึงมีประโยชน์เมื่อ:

  • คุณต้องทำสิ่งที่เรียบง่าย (หรือสิ่งที่ง่าย);

    เครื่องกำเนิดไฟฟ้านั้นง่ายกว่ามากและใช้อินเทอร์เฟซ Iterator ในทางกลับกันคือแหล่งที่มาของเครื่องกำเนิดไฟฟ้าที่ทำงานได้น้อยลง เปรียบเทียบพวกเขา

  • คุณจำเป็นต้องสร้างหน่วยความจำบันทึกข้อมูลจำนวนมาก

    จริงๆแล้วเพื่อบันทึกหน่วยความจำเราสามารถสร้างข้อมูลที่ต้องการผ่านฟังก์ชั่นสำหรับการวนซ้ำทุกครั้งและหลังจากการวนซ้ำใช้ขยะ ดังนั้นที่นี่ประเด็นหลักคือ - รหัสที่ชัดเจนและอาจมีประสิทธิภาพ ดูว่ามีอะไรดีกว่าสำหรับความต้องการของคุณ

  • คุณต้องสร้างลำดับซึ่งขึ้นอยู่กับค่ากลาง

    นี่คือการขยายความคิดก่อนหน้านี้ เครื่องกำเนิดไฟฟ้าสามารถทำให้สิ่งต่าง ๆ ง่ายขึ้นเมื่อเทียบกับฟังก์ชั่น ตรวจสอบตัวอย่างฟีโบนักชีและพยายามสร้างลำดับโดยไม่มีตัวสร้าง เครื่องกำเนิดไฟฟ้ายังสามารถทำงานได้เร็วขึ้นเป็นกรณีนี้อย่างน้อยก็เพราะการจัดเก็บค่ากลางในตัวแปรท้องถิ่น

  • คุณต้องปรับปรุงประสิทธิภาพ

    พวกเขาสามารถทำงานได้เร็วขึ้นแล้วฟังก์ชั่นในบางกรณี (ดูประโยชน์ก่อนหน้า);


1
ฉันไม่เข้าใจว่าเครื่องกำเนิดไฟฟ้าทำงานอย่างไร คลาสนี้ใช้อินเตอร์เฟสตัววนซ้ำ จากสิ่งที่ฉันรู้ว่าคลาสตัววนซ้ำอนุญาตให้ฉันกำหนดค่าวิธีที่ฉันต้องการวนซ้ำวัตถุ ตัวอย่างเช่น ArrayIterator ได้รับอาร์เรย์หรือวัตถุเพื่อให้ฉันสามารถปรับเปลี่ยนค่าและคีย์ในขณะที่ทำซ้ำ ดังนั้นหากตัววนซ้ำได้รับทั้งออบเจ็กต์ / อาเรย์แล้วตัวสร้างไม่ต้องสร้างอาเรย์ทั้งหมดในหน่วยความจำได้อย่างไร
user3021621

7

ด้วยyieldคุณสามารถอธิบายจุดพักระหว่างหลาย ๆ งานในฟังก์ชั่นเดียวได้อย่างง่ายดาย นั่นคือทั้งหมดที่ไม่มีอะไรพิเศษเกี่ยวกับมัน

$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, ...);

หาก task1 และ task2 มีความเกี่ยวข้องสูง แต่คุณต้องการเบรกพอยต์ระหว่างพวกเขาเพื่อทำสิ่งอื่น:

  • หน่วยความจำฟรีระหว่างการประมวลผลแถวฐานข้อมูล
  • รันงานอื่นที่ให้การอ้างอิงกับภารกิจถัดไป แต่ไม่เกี่ยวข้องกับการทำความเข้าใจโค้ดปัจจุบัน
  • กำลังเรียก async และรอผล
  • และอื่น ๆ ...

เครื่องปั่นไฟเป็นทางออกที่ดีที่สุดเพราะคุณไม่จำเป็นต้องแยกรหัสของคุณออกเป็นหลายการปิดหรือผสมกับรหัสอื่นหรือใช้การโทรกลับ ฯลฯ ... คุณเพียงแค่ใช้yieldเพื่อเพิ่มเบรกพอยต์และคุณสามารถทำต่อได้ เบรกพอยต์ถ้าคุณพร้อม

เพิ่มเบรกพอยต์โดยไม่มีเครื่องกำเนิดไฟฟ้า:

$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);
//...

เพิ่มเบรกพอยต์กับเครื่องกำเนิดไฟฟ้า

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

หมายเหตุ: ง่ายต่อการทำผิดพลาดกับเครื่องกำเนิดไฟฟ้าดังนั้นควรเขียนการทดสอบหน่วยก่อนที่จะใช้พวกเขา! Note2: การใช้เครื่องกำเนิดไฟฟ้าในวงวนไม่สิ้นสุดก็เหมือนกับการเขียนแบบปิดซึ่งมีความยาวไม่สิ้นสุด ...


4

ไม่มีคำตอบข้างต้นที่แสดงตัวอย่างที่เป็นรูปธรรมโดยใช้อาร์เรย์ขนาดใหญ่ที่มีสมาชิกที่ไม่ใช่ตัวเลข นี่คือตัวอย่างการใช้อาร์เรย์ที่สร้างขึ้นโดยexplode()ไฟล์. txt ขนาดใหญ่ (262MB ในกรณีที่ใช้งานของฉัน):

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

ผลลัพธ์คือ:

Starting memory usage: 415160
Final memory usage: 270948256

ตอนนี้เปรียบเทียบกับสคริปต์ที่คล้ายกันโดยใช้yieldคำสำคัญ:

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

ผลลัพธ์สำหรับสคริปต์นี้คือ:

Starting memory usage: 415152
Final memory usage: 415616

เห็นได้ชัดว่าการประหยัดการใช้หน่วยความจำมีความสำคัญมาก (emMemoryUsage -----> ~ 270.5 MBในตัวอย่างแรก, ~ 450Bในตัวอย่างที่สอง)


3

ด้านที่น่าสนใจซึ่งคุ้มค่าที่จะได้รับการกล่าวถึงที่นี่เป็นผลผลิตจากการอ้างอิง ทุกครั้งที่เราจำเป็นต้องเปลี่ยนพารามิเตอร์เพื่อให้มันสะท้อนอยู่ด้านนอกของฟังก์ชั่นเราจะต้องผ่านพารามิเตอร์นี้โดยการอ้างอิง ในการใช้สิ่งนี้กับเครื่องกำเนิดไฟฟ้าเราเพียงใส่เครื่องหมาย&และชื่อของเครื่องกำเนิดไฟฟ้าและตัวแปรที่ใช้ในการทำซ้ำ:

 <?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...

ตัวอย่างข้างต้นแสดงวิธีการเปลี่ยนค่าที่วนซ้ำภายในforeachลูปจะเปลี่ยน$fromตัวแปรภายในตัวสร้าง นี้เป็นเพราะ$fromเป็นผลจากการอ้างอิงเนื่องจากเครื่องหมายหน้าชื่อเครื่องกำเนิดไฟฟ้า เนื่องจากสิ่งนั้น$valueตัวแปรภายในforeachลูปจึงอ้างอิงถึง$fromตัวแปรภายในฟังก์ชันตัวสร้าง


0

รหัสด้านล่างแสดงให้เห็นว่าการใช้เครื่องกำเนิดไฟฟ้าส่งกลับผลลัพธ์ก่อนที่จะเสร็จสิ้นซึ่งแตกต่างจากวิธีการที่ไม่ใช่เครื่องกำเนิดไฟฟ้าแบบดั้งเดิมที่ส่งกลับอาร์เรย์ที่สมบูรณ์หลังจากการทำซ้ำเต็ม ด้วยตัวสร้างด้านล่างค่าจะถูกส่งคืนเมื่อพร้อมไม่จำเป็นต้องรอให้อาร์เรย์เต็ม:

<?php 

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

foreach (sleepiterate(5) as $i) {
    echo $i, PHP_EOL;
}

ดังนั้นมันเป็นไปไม่ได้ที่จะใช้ผลผลิตเพื่อสร้างรหัส html ใน php? ฉันไม่ทราบถึงประโยชน์ในสภาพแวดล้อมจริง
Giuseppe Lodi Rizzini

@GiuseppeLodiRizzini อะไรที่ทำให้คุณคิดอย่างนั้น?
แบรดเคนต์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.