ข้ามไปยังเนื้อหา

Iterators

ก่อน Go 1.23 function ที่ผลิตลำดับข้อมูลมีทางเลือกที่ไม่สวยอยู่สามทาง: สร้างแล้วคืน slice ทั้งก้อน (allocate ทุกอย่างตั้งแต่แรก), ส่ง channel ออกมา (ต้องเปิด goroutine และช้า) หรือคิด type Next() ของตัวเองที่ range ไม่รู้จัก Go 1.23 เพิ่ม range-over-func: for วนซ้ำบน function ที่มีรูปแบบเฉพาะได้ และ standard library ได้ package iter มาตั้งชื่อรูปแบบเหล่านั้น

ผลลัพธ์คือลำดับข้อมูลแบบ lazy ที่ไม่ allocate และใช้กับ syntax ของ loop ที่คุณรู้จักอยู่แล้ว

package iter นิยาม generic function type ไว้สองแบบ:

package iter
type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)

iterator คือ function ที่รับ callback ชื่อ yield มันเรียก yield หนึ่งครั้งต่อ element; yield คืน false เมื่อผู้ใช้ต้องการหยุด และ iterator ต้อง return ทันทีในกรณีนั้น Seq2 yield เป็นคู่ — index กับ value หรือ key กับ value — ล้อกับ range แบบสองตัวแปร

นี่คือ counter ที่ yield 0..n-1 และหยุดก่อนจบเมื่อถูกขอ:

func Count(n int) iter.Seq[int] {
return func(yield func(int) bool) {
for i := 0; i < n; i++ {
if !yield(i) {
return // consumer broke out of the loop
}
}
}
}
for v := range Count(5) {
if v == 3 {
break // yield returns false inside Count, which returns
}
fmt.Println(v)
}

break, return และ continue ใน loop body ทำงานตามที่คาดทุกอย่าง: compiler แปลง body ให้เป็น function yield และแปลง control flow ให้เอง iterator เห็นแค่ yield คืน false

เพราะลำดับถูกผลิตตามความต้องการ Count(1_000_000_000) จึงไม่มีต้นทุนจนกว่าจะเริ่มวน — และถ้าหยุดหลังสามตัว ก็ผลิตแค่สามตัว

กฎ yield == false จากหัวข้อก่อนหน้าไม่ใช่แค่คำแนะนำ — iterator ที่ไม่สนใจมันแล้วยังเรียก yield ต่อหลังจากที่มันคืน false ไปแล้วครั้งหนึ่งคือ runtime error ไม่ใช่แค่ bug ทาง logic:

func Count(n int) iter.Seq[int] {
return func(yield func(int) bool) {
for i := 0; i < n; i++ {
yield(i) // BUG: ignores yield's return value and keeps going
}
}
}

ตรวจสอบแล้วบน 1.26.5 การเรียกโค้ดนี้แล้ว break ออกจาก loop range ก่อนจบจะ panic:

panic: runtime error: range function continued iteration after function for loop body returned false

Go runtime ติดตามว่า loop body ขอหยุดไปแล้วหรือยังและบังคับใช้กฎนี้ — iterator ที่เขียนเองทุกตัวต้องเช็กค่าที่ yield คืนกลับมาและ return ทันทีเมื่อมันเป็น false เหมือนที่ Count ทำไว้ก่อนหน้านี้ในบทเรียน

defer ที่เขียนไว้ข้างใน body ของ for ... range someIterator ไม่ได้รันตอนจบแต่ละ iteration — มันรันตอนที่ function ที่ครอบมันอยู่ return เพราะ loop body ถูก compile เป็น closure ที่ iterator เรียกซ้ำ ๆ และ defer ทุกตัวข้างในนั้นถูกผูกกับ function ที่มี for loop อยู่ ไม่ใช่ผูกกับ closure เอง:

func process() {
for v := range Count(3) {
defer fmt.Println("deferred for", v) // queues up, doesn't run yet
fmt.Println("processing", v)
}
fmt.Println("loop finished")
}

ตรวจสอบแล้วบน 1.26.5 โค้ดนี้พิมพ์ทั้งสามบรรทัด "processing" และ "loop finished" ก่อน บรรทัด "deferred for" ใด ๆ — deferred call ทั้งสามตัวสะสมข้าม iteration และรันตามลำดับ LIFO ก็ต่อเมื่อ process เอง return โค้ดที่คาดหวังว่า defer ข้างใน range-over-func loop จะทำงานเหมือน defer ตอนจบ iteration เดียว (เหมือนเป็น body ของ for loop ธรรมดา) จะแปลกใจ — ให้ใช้ helper function ต่อ iteration หรือเรียก cleanup ตรง ๆ แทน defer ถ้าต้องการให้มันเกิดขึ้นทุกรอบของ loop

รูปแบบ idiomatic คือ method ชื่อ All, Keys, Values หรือทำนองนั้น ที่คืน iterator แทน slice:

type List[T any] struct{ items []T }
func (l *List[T]) All() iter.Seq2[int, T] {
return func(yield func(int, T) bool) {
for i, v := range l.items {
if !yield(i, v) {
return
}
}
}
}
for i, v := range list.All() {
fmt.Println(i, v)
}

caller ไม่ต้องแตะ backing slice และคุณเปลี่ยน representation ทีหลังได้อิสระ — linked list, database cursor, HTTP API แบบแบ่งหน้า — โดย loop ไม่ต้องเปลี่ยนเลย

ไม่มีอะไรใน type ของ iter.Seq บังคับให้ลำดับต้องรองรับการ range ซ้ำมากกว่าหนึ่งครั้ง — ขึ้นอยู่กับสิ่งที่ closure ทำทั้งหมด List.All() ด้านบนเป็น re-iterable: ทุกการเรียกสร้าง closure ใหม่ที่อ่าน l.items ตั้งแต่ต้น ส่วนลำดับที่อิงกับสิ่งที่ถูก “ใช้ไป” ตอนอ่าน — channel, network connection, bufio scanner — เป็น single-pass: เมื่อดึงหมดแล้ว การ range ค่า iter.Seq ตัวเดิมอีกครั้งจะไม่ให้อะไรเลย เงียบ ๆ:

func FromChannel(ch <-chan int) iter.Seq[int] {
return func(yield func(int) bool) {
for v := range ch {
if !yield(v) {
return
}
}
}
}

ตรวจสอบแล้วบน 1.26.5: การ range seq := FromChannel(ch) ตัวเดิมสองครั้งจะพิมพ์ค่าของ channel ในรอบแรก แล้วไม่พิมพ์อะไรเลยในรอบที่สอง — ไม่มี error ไม่มี panic แค่ loop ที่ว่างเปล่า เพราะ channel ถูกดึงจนหมดไปแล้ว เมื่อ function คืนค่า iter.Seq ไม่มีอะไรใน type บอกว่ามัน range ซ้ำได้ปลอดภัยหรือเปล่า ต้องเช็ก doc comment หรือ source เหมือนที่ทำกับ function ที่คืน *bufio.Reader

ตั้งแต่ Go 1.23 package slices และ maps (ส่วน strings/bytes ตั้งแต่ Go 1.24) ทั้ง ผลิต และ ใช้ iterator:

import ("maps"; "slices"; "strings")
// consume: turn an iterator into a slice
evens := slices.Collect(filter(Count(10), isEven)) // []int
keys := slices.Sorted(maps.Keys(m)) // sorted keys, one line
// produce: iterate without copying
for i, v := range slices.All(xs) { /* index, value */ }
for v := range slices.Values(xs) { /* value only */ }
for k, v := range maps.All(m) { /* key, value */ }
// strings (Go 1.24): lazy splitting
for line := range strings.Lines(text) { /* each line, newline kept */ }
for part := range strings.SplitSeq(csv, ",") { /* no []string allocated */ }

slices.Collect, slices.Sorted, slices.SortedFunc, maps.Collect และ maps.Insert คือสะพานกลับสู่ข้อมูลจริง ส่วน strings.SplitSeq และ strings.FieldsSeq คือฝาแฝดแบบ lazy ของ Split และ Fields

helper อีกไม่กี่ตัวที่ควรรู้จักชื่อไว้:

Functionผลิตอะไร
slices.Backward(s)iter.Seq2[int, E] — คู่ index/value เรียงจาก index มากไปน้อย
slices.Chunk(s, n)iter.Seq[Slice] — sub-slice ต่อเนื่องขนาดไม่เกิน n element
maps.Values(m)iter.Seq[V] — เฉพาะ value เท่านั้น ลำดับไม่กำหนด
maps.Keys(m)iter.Seq[K] — เฉพาะ key เท่านั้น ลำดับไม่กำหนด

ก่อน Go 1.23 maps.Keys (ใน golang.org/x/exp/maps) คืน []K แต่ version ใน standard library คืน iter.Seq[K] แทน — เป็น function ไม่ใช่ slice — ดังนั้นโค้ดที่เขียนตามรูปแบบเก่าจะพังตอน compile time แทนที่จะทำงานผิดเงียบ ๆ:

m := map[string]int{"z": 1, "a": 2}
sort.Strings(maps.Keys(m)) // BUG: maps.Keys returns iter.Seq[string], not []string

ตรวจสอบแล้วบน 1.26.5 นี่คือ compile error:

cannot use maps.Keys(m) (value of func type iter.Seq[string]) as []string value in argument to sort.Strings

ทางแก้ปรากฏอยู่แล้วก่อนหน้านี้ในบทเรียน: slices.Sorted(maps.Keys(m)) รวบรวม iterator ให้เป็น []string ที่ sort แล้วในการเรียกครั้งเดียว แทนที่รูปแบบเก่าสองขั้นตอน “รวบรวมเป็น slice แล้วค่อย sort.Strings” ไปเลย

filter เล็ก ๆ ไว้อ้างอิง — combinator แบบนี้ประกอบกันได้เป็นธรรมชาติ:

func filter[T any](seq iter.Seq[T], keep func(T) bool) iter.Seq[T] {
return func(yield func(T) bool) {
for v := range seq {
if keep(v) && !yield(v) {
return
}
}
}
}

range-over-func เป็นโมเดลแบบ push: ฝั่งผู้ผลิตเป็นคนขับ ส่วน loop body ของผู้ใช้เป็น callback เมื่อต้องดึงทีละ element เอง — เช่น merge ลำดับที่ sort แล้วสองชุด — iter.Pull แปลง push iterator ให้เป็นคู่ next()/stop():

next, stop := iter.Pull(Count(3))
defer stop()
v, ok := next() // 0, true

iter.Pull ใช้ coroutine อยู่ข้างใน จึงช้ากว่า range ธรรมดา ใช้เฉพาะเมื่อจำเป็นต้องได้รูปแบบ pull จริง ๆ

flowchart LR
  subgraph Push["push (for ... range seq)"]
    Producer["iterator drives"] --> Body["loop body runs as yield callback"]
  end
  subgraph Pull["pull (iter.Pull)"]
    Caller["caller drives"] -->|"next()"| Coroutine["iterator's coroutine resumes to the next yield"]
    Coroutine -->|"value, true"| Caller
    Caller -->|"stop()"| Coroutine
    Coroutine -->|"releases resources"| Done["coroutine exits"]
  end
Push: iterator เป็นฝ่ายเรียกเรา Pull: เราเป็นฝ่ายเรียก iterator และต้องเรียก stop() เมื่อเสร็จ

contract อย่างเป็นทางการจาก doc ของ package iter: stop “must be called when the caller is no longer interested in next values and next has not yet signaled that the sequence is over” ในทางปฏิบัติ iter.Pull รัน push iterator เดิมบน coroutine ที่ suspend ตัวเองระหว่างแต่ละค่า stop() คือสิ่งที่บอกให้ coroutine นั้น unwind และปล่อยสิ่งที่มันถืออยู่ — ถ้าไม่เรียก การใช้ next() แล้วเลิกกลางทางจะทำให้ coroutine ค้างอยู่แบบนั้นตลอดไป การเรียก stop ซ้ำหลายครั้งถูกต้องเสมอ และเรียก next หลัง stop ก็ถูกต้องเช่นกัน (มันจะคืน zero value กับ false ต่อไป) — นี่คือเหตุผลที่ defer stop() ทันทีหลัง iter.Pull เป็น pattern มาตรฐาน เหมือน reflex เดียวกับ defer resp.Body.Close() หรือ defer f.Close()

  • คืน slice เมื่อข้อมูลเป็น slice อยู่แล้ว และมีขนาดเล็ก iterator ไม่ได้เพิ่มอะไรตรงนั้น
  • คืน iterator เมื่อลำดับมีขนาดใหญ่ ไม่มีที่สิ้นสุด ผลิตแพง มี I/O อยู่ข้างหลัง หรือเมื่อต้องการซ่อน representation
  • เคารพ yield == false เสมอ iterator ที่ยังเรียก yield ต่อหลังจากมันคืน false จะ panic
  • ตั้งชื่อ accessor ว่า All, Keys, Values, Backward ให้ตรงกับ standard library
  • iterator เป็น function ธรรมดา: ไม่ได้ปลอดภัยต่อ goroutine โดยอัตโนมัติ และถือทุกอย่างที่ closure จับไว้

ทุกอย่างข้างบนรวมอยู่ในโปรแกรมเดียวที่รันได้ — Count, filter, List.All และตัวเชื่อม slices/maps:

package main
import (
"fmt"
"iter"
"maps"
"slices"
)
// Count yields 0, 1, …, n-1 lazily.
func Count(n int) iter.Seq[int] {
return func(yield func(int) bool) {
for i := 0; i < n; i++ {
if !yield(i) {
return // the consumer broke out of its loop
}
}
}
}
func filter[T any](seq iter.Seq[T], keep func(T) bool) iter.Seq[T] {
return func(yield func(T) bool) {
for v := range seq {
if keep(v) && !yield(v) {
return
}
}
}
}
type List[T any] struct{ items []T }
func (l *List[T]) All() iter.Seq2[int, T] {
return func(yield func(int, T) bool) {
for i, v := range l.items {
if !yield(i, v) {
return
}
}
}
}
func main() {
isEven := func(n int) bool { return n%2 == 0 }
fmt.Println(slices.Collect(filter(Count(10), isEven)))
for v := range Count(100) {
if v > 2 {
break // Count returns as soon as yield reports false
}
fmt.Println("got", v)
}
list := &List[string]{items: []string{"a", "b", "c"}}
for i, v := range list.All() {
fmt.Println(i, v)
}
m := map[string]int{"z": 26, "a": 1, "m": 13}
fmt.Println(slices.Sorted(maps.Keys(m)))
}

เมื่อโค้ดที่ AI generate เขียนหรือใช้ iterator ให้มองหาสิ่งเหล่านี้ก่อนเชื่อมัน:

  • iterator ที่เขียนเองเพิกเฉยต่อค่าที่ yield คืนกลับมาแล้วยัง loop ต่อหลังจากมันคืน false หรือเปล่า? นั่นคือ runtime panic ไม่ใช่ bug ที่เงียบ
  • sort.Strings, sort.Ints หรือคล้ายกันถูกเรียกตรง ๆ กับผลลัพธ์ของ maps.Keys/maps.Values/slices.Values หรือเปล่า? สิ่งเหล่านั้นคืน iter.Seq ไม่ใช่ slice — compile ไม่ผ่าน
  • defer ข้างใน range-over-func loop body ถูกสมมติว่ารันครั้งเดียวต่อ iteration สำหรับ cleanup ที่ต้องเกิดทุกรอบของ loop หรือเปล่า?
  • โค้ด range ค่า iter.Seq เดิมซ้ำเป็นครั้งที่สอง โดยคาดหวังผลลัพธ์เดิม ทั้งที่ source ข้างใต้ (channel, scanner, network read) ดึงได้แค่ครั้งเดียวหรือเปล่า?
  • iter.Pull ถูกใช้โดยไม่มี defer stop() ครอบทุก return path รวมถึง early return และ error หรือเปล่า?

🐛 หา bug ในโค้ดนี้

func Count(n int) iter.Seq[int] {
return func(yield func(int) bool) {
for i := 0; i < n; i++ {
yield(i)
}
}
}
func main() {
for v := range Count(5) {
if v == 2 {
break
}
fmt.Println(v)
}
}
เฉลย

iterator ของ Count เรียก yield(i) แต่ไม่เช็กค่าที่คืนกลับมาเลย จึงยังเรียก yield ต่อทุก i ที่เหลือ แม้ loop จะ break ออกไปแล้วก็ตาม ตรวจสอบแล้วบน 1.26.5: โค้ดนี้ panic ด้วย panic: runtime error: range function continued iteration after function for loop body returned false ทางแก้คือ if !yield(i) { return } เหมือนที่ Count เวอร์ชันถูกต้องก่อนหน้านี้ในบทเรียนทำ

package main
import (
"fmt"
"iter"
)
func Count(n int) iter.Seq[int] {
return func(yield func(int) bool) {
for i := 0; i < n; i++ {
if !yield(i) {
return
}
}
}
}
// process shows that defer inside a range-over-func body queues up for the
// whole enclosing function, not per iteration -- it runs LIFO when process
// itself returns.
func process() {
for v := range Count(3) {
defer fmt.Println("deferred for", v)
fmt.Println("processing", v)
}
fmt.Println("loop finished")
}
// FromChannel is single-pass: once the channel is drained, ranging over the
// same Seq again yields nothing.
func FromChannel(ch <-chan int) iter.Seq[int] {
return func(yield func(int) bool) {
for v := range ch {
if !yield(v) {
return
}
}
}
}
func main() {
process()
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
seq := FromChannel(ch)
fmt.Println("first pass:")
for v := range seq {
fmt.Println(v)
}
fmt.Println("second pass on the same (now-drained) sequence:")
for v := range seq {
fmt.Println(v)
}
fmt.Println("second pass produced nothing -- the channel has no more values")
// iter.Pull for manual, one-at-a-time control.
next, stop := iter.Pull(Count(3))
defer stop() // required: releases the iterator's underlying coroutine
for {
v, ok := next()
if !ok {
break
}
fmt.Println("pulled", v)
}
}
iter.Seq[V] นิยามว่าอย่างไร?
โค้ดนี้ compile ผ่านไหม?
sort.Strings(maps.Keys(m))
จะเกิดอะไรขึ้นเมื่อโค้ดนี้รัน?
func Count(n int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 0; i < n; i++ {
            yield(i)
        }
    }
}

for v := range Count(5) {
    if v == 2 {
        break
    }
}
จะเกิดอะไรขึ้นตอน range ครั้งที่สอง?
seq := FromChannel(ch) // reads from a channel until it's closed
for v := range seq { /* ... */ }
for v := range seq { /* ... */ } // ranging again
ทำไม defer stop() ควรตามหลังทุกการเรียก iter.Pull?
เมื่อไรที่การคืน slice ธรรมดาเป็นการออกแบบที่ดีกว่า?