Go

How do generics combine with Go 1.23 iterators (iter.Seq)? Write lazy Filter and Map adapters and explain the yield contract.

Question 140HardGo 1.22 to 1.25

iter.Seq[V] is a generic function type, func(yield func(V) bool), and iter.Seq2[K, V] is the two-value form. for v := range seq calls seq with a compiler-generated yield. Adapters are generic functions that wrap one sequence in another. They are lazy: no intermediate slices are built, and they can be infinite.

func Filter[V any](seq iter.Seq[V], keep func(V) bool) iter.Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {
            if keep(v) && !yield(v) {
                return // consumer stopped: MUST stop
            }
        }
    }
}

func Map[V, U any](seq iter.Seq[V], f func(V) U) iter.Seq[U] {
    return func(yield func(U) bool) {
        for v := range seq {
            if !yield(f(v)) {
                return
            }
        }
    }
}

func Naturals() iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 0; ; i++ {
            if !yield(i) {
                return
            }
        }
    }
}

for s := range Map(Filter(Naturals(), func(n int) bool { return n%3 == 0 }), strconv.Itoa) {
    if s == "12" {
        break
    }
    fmt.Print(s, " ") // 0 3 6 9
}

The contract: when yield returns false (because the loop body executed break, return or goto), the iterator must stop and must never call yield again. Violating that causes a runtime panic: "range function continued iteration after function for loop body returned false". Also, defer inside the iterator runs when the iterator function returns, which makes it a good place to release resources.

More on Generics

All 36 Generics questions