เรียนเขียนโปรแกรมง่ายๆกับ Expert Programming Tutor ในบท การสร้าง generator และการใช้งาน iterable
ในภาษา Dart (และภาษาสมัยใหม่อื่นๆ ด้วย) จะมีตัวแปรอีกชนิดนึงที่ "สามารถนำมาวนลูปได้" หรือ "สามารถ access ค่าเป็นลำดับเรียงต่อกันได้" ตามปกติเราสามารถสร้างลิสต์ได้แบบนี้
|
แต่ถ้าเราลองเข้าไปดู source code ของคลาส List เราจะพบว่ามัน extends มาจากคลาสๆ หนึ่งที่มีชื่อว่า Iterable แปลว่าเราสามารถสร้างลิสต์แบบนี้ได้
|
ซึ่งทั้ง 2 แบบสามารถเอามาวน loop ได้ด้วยนะ
|
ได้ผลเหมือนกันเลย แล้วถ้ามันทำได้เหมือนกันแบบนี้มันจะมี 2 คลาสทำไมกัน แปลว่ามันต้องมีความต่างกันล่ะ และนั่นแหละ คือหัวข้อที่เราจะมาพูดกันในบทความนี้ คือเรื่องของ Generator และ Iterable สิ่งที่เราต้องเตรียมคือฟังก์ชันที่รับ index มาแล้วตอบค่าของ item ในตำแหน่งนั้นกลับไป
|
แต่ลองดูโค้ดต่อไปนี้
|
คำถามคือ...ถ้าเอาโค้ดแค่นี้เลยนะ มารัน -> จะได้ output ออกมาเป็นยังไง?
คำตอบคือ...
|
นั่นคือฟังก์ชันสำหรับสร้างไอเทมของ List ถูกรันจนครบ 3 ครั้งเลย แต่กลับกันคือ Iterable นั้นไม่ถูกรันเลยซักครั้ง! แต่ถ้าเราเขียนโค้ดต่อไปอีกหน่อย เป็นแบบนี้
|
output กลับได้เป็น
|
เราจะพบว่าฟังก์ชันของ Iterable เริ่มทำงานแล้วล่ะนะ แต่ทำงานแค่ครั้งเดียว!?
ถ้าจำลองสถานการณ์ของโค้ดเมื่อกี๊
- สำหรับ List: เปิดมา ไม่ต้องพูดพร่ำทำเพลง สร้างมันรวดเดียวเลย 3 ไอเทม (ยังไม่มีคนเรียกใช้เลยนะ สร้างไว้ก่อน)
- สำหรับ Iterable: เปิดมา ยังไม่ทำอะไรทั้งนั้นแหละ (Lazy) แต่เมื่อมีการเรียกไอเทม elementAt(0) มันพบว่าไอเทมตัวที่ 0 น่ะยังไม่ถูกสร้างขึ้นมาเลย แต่เขาจะใช้งานแล้วนี่นา -> งั้นก็สร้างซะตอนนี้เลย!
แต่อ่านมาถึงตรงนี้ เราก็อาจจะงงๆ สงสัยกันว่า แล้วจะทำแบบนี้เพื่ออะไรกัน?
ลองดูตัวอย่างต่อไป...
สร้าง Iterable ด้วยฟังก์ชัน Generator
สมมุติว่าเราจะสร้างฟังก์ชันสำหรับสร้างเลข 1-1,000,000 (หนึ่งถึงหนึ่งล้าน) ขึ้นมาหนึ่งตัวเพื่อเอาไปวนลูปอะไรสักอย่างนึง
|
ทั่วๆ ไปเราก็อาจจะนึกถึงฟังก์ชันที่สร้าง List คืนมา แบบนี้
|
ดังนั้นเวลาโค้ดทำงาน
ฟังก์ชันสร้างตัวเลขทั้ง 1 ล้านตัวขึ้นมาก่อน
ส่งกลับไปให้ลูปทำการวนปริ๊นค่าทั้ง 1 ล้านตัวนั่นออกมา
แต่ปัญหาจะเกิดขึ้น ถ้าลูปของเรามันสามารถหยุดกลางคันได้
|
ทีนี้ โค้ดก็จะทำงานเปลี่ยนไป
1.ฟังก์ชันสร้างตัวเลขทั้ง 1 ล้านตัวขึ้นมาก่อน
2.ส่งกลับไปให้ลูปทำการวนปริ๊นค่า
3.แต่คราวนี้ ปริ๊นไปแค่ 10 ตัวก็จบลูปแล้ว (ตัวเลขที่เหลืออีก 999,900 ก็ไม่ได้ใช้ แต่สร้างมาแล้วนะ)
ถ้าถามว่าโลจิคแบบนี้มันโอเคมัน มันก็ได้อยู่นะ โลจิคไม่ได้เปลี่ยนไป แต่ในแง่ประสิทธิภาพ performance ของโปรแกรมนี่ไม่ผ่านแน่นอน เพราะแบบนี้ ถ้าเราสร้างฟังก์ชัน getNumbers() ด้วย Iterable จะทำให้ประหยัด performance มากๆ เพราะถึงจะกำหนดว่าต้องสร้างเลข 1-1,000,000 แต่เวลาเรียกใช้ ใช้แค่10ตัว มันก็จะสร้างไอเทมขึ้นมาแค่10เท่าที่ต้องใช้ ไม่ต้องสร้างจริงทั้งหนึ่งล้านตัว
sync* ฟังก์ชันที่หยุดกลางคันได้
นอกจากวิธีใช้ factory function Iterable.generate สร้าง Iterable ขึ้นมา จริงๆ ยังมีอีกวิธีในการสร้างมัน นั่นคือการใช้ "Generator Function" ถ้าเราสร้างฟังก์ชันที่ตอบ int ธรรมดาก็จะได้โค้ดหน้าตาแบบนี้
|
จุดสังเกตคือมี 2 อย่างที่เปลี่ยนไป นั่นคือ
- เราไม่ได้ใช้คีย์เวิร์ด return อีกต่อไป แต่ใช้ yield แทน
- มีการกำหนดฟังก์ชันเป็น sync* ด้วยนะ (จะมาพร้อม yield เสมอ คือถ้าต้องการใช้คำสั่ง yield ในฟังก์ชันจะต้องประกาศให้ฟังก์ชันเป็น sync* เสมอ) ความแตกต่างระหว่าง yield กับ return คือทันทีที่ return ทำงาน ฟังก์ชันจะจบการทำงานทันที แต่สำหรับ yield นั้นจะยังไม่หยุดจนกว่าจะจบฟังก์ชัน เช่น
|
เครดิต https://dev.to/centrilliontech/dart-106-generator-iterable-2oof
Tag ที่น่าสนใจ: dart programming iterable generator list source-code performance factory-function sync yield indexing looping
หากมีข้อผิดพลาด/ต้องการพูดคุยเพิ่มเติมเกี่ยวกับบทความนี้ กรุณาแจ้งที่ http://m.me/Expert.Programming.Tutor
085-350-7540 (DTAC)
084-88-00-255 (AIS)
026-111-618
หรือทาง EMAIL: NTPRINTF@GMAIL.COM