TGViewer
Daria’s room Daria’s room @dariasroom · 1.2K subscribers
Post #125 1.34K
Итераторы…

TL;DR: в iter.Seq итератор сам передаёт следующие элементы в код внутри range, а через yield получает сигнал и решает, продолжать ли обход. Ошибку можно передавать вторым значением через Seq2[T, error], но тогда, если она должна дойти до конца цепочки, все промежуточные итераторы тоже должны уметь её принять и передать дальше, даже если она им не нужна (!). Стоит ли оно такой запары?

Вспомним обычный обход:

for i := 0; i < source.Len(); i++ {
process(source.Get(i))
}

Это типичная pull-модель: внешний код сам сначала вытаскивает значение из источника Get(i), а потом передаёт его в бизнес-логику process.

Обратный вариант - обход с callback функцией:

source.Scan(func(v Value) bool {
process(v) // принимает то, что будет попадать из цикла ниже
return true
})

func (s *Source) Scan(process func(Value) bool) {
for i := 0; i < s.Len(); i++ {
if !process(s.Get(i)) {
return
}
}
}


Внешний код вызывает Scan один раз, а дальше уже Scan достает данные и сам вызывает коллбек process - в ответ получает bool и принимает решение об остановке. С точки зрения вызывающего кода это push-модель.

Многострадальные итераторы работают по тому же принципу, только такой callback-обход стандартизирован общим типом iter.Seq


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


Коллбек-механика спрятана внутри range:


go
for v := range Numbers(10) {
// бизнес логика тут, а вызывает ее код из Numbers
if v == 5 {
break
}
}


Numbers сам вызывает yield с очередным значением. Код внутри range получает значение, обрабатывает (в нашем случае сравнивает с 5), и отдает bool итератору, где последний решает, продолжать ли обход.

С остановкой по инициативе коллбека внутри range всё ок. Но что, если продолжить обход не может уже сам итератор - например, если чтение следующего значения закончилось ошибкой.

В Seq[T] передать её некуда. Один из вариантов - вынести ошибку во второе значение:


func Values() iter.Seq2[int, error] {
return func(yield func(int, error) bool) {
for i := 0; i < 10; i++ {
v, err := readValue(i)
if err != nil {
yield(0, err)
return
}

if !yield(v, nil) {
return
}
}
}
}


Тогда ошибка приходит в тот же range, что и данные:


for v, err := range Values() {
if err != nil {
return err
}

process(v)
}


Для одного итератора это выглядит естественно. Но в композиции с другими итераторами, например c


func Double(seq iter.Seq[int]) iter.Seq[int] {
return func(yield func(int) bool) {
for v := range seq {
if !yield(v * 2) {
return
}
}
}
}


eсли ошибка из Values должна пройти через Double до конечного потребителя, то`Double` (который сам упасть не может и которому не нужна ошибка), вынужден перейти на Seq2[int, error] (иначе у нас просто не скомпилится тк Double принимает `Seq[int]`) и явно передавать чужую ошибку

Чтобы не разводить зоопарк предложили даже собирать такие ошибки через отдельный хелпер`slices.CollectError` в #70631


func CollectError[T any](seq iter.Seq2[T, error]) ([]T, error) {
var err error

result := slices.Collect(func(yield func(T) bool) {
for v, e := range seq {
if e != nil {
// записываем первую ошибку
err = e
// завершаем обход
return
}

if !yield(v) {
return
}
}
})

return result, err
}


Просто как кейс стандартизации обработки ошибки через Seq2[T, error]. Или очередной способ задать себе правила 😗

Еще нашла #71901 где решают, какой контракт для итераторов с ошибками стоит считать стандартным. Но мне страшно туда лезть..

#go
GitHub proposal: slices: add CollectError · Issue #70631 · golang/go Proposal Details Idiomatic go functions are often in the form func (...) (T, err). When creating iterators, it is equally common for the underlying functions to potentially return an error as each ...
  • ❤ 14
  • 🔥 5
  • 👍 3
  • 😱 2
More from @dariasroom
  1. Sep 15, 2026Автоматическое сжатие на клиенте vs ручное на сервере Стандартный клиент http.Transport са…
  2. Sep 5, 2026Причина перекосов в уровне балансировки L4-балансировщик выбирает серверную ноду при созда…
  3. Aug 17, 2026Что на самом деле нужно сохранять при сериализации сложной структуры? TL;DR: Важно отделит…
  4. Aug 13, 2026Привет! Вас стало больше, так что пора наконец представиться 🙂 Я Даша, давно пишу на Go,…
  5. Aug 12, 2026HNSW: как устроен графовый индекс для векторного поиска One million years later, я наконец…
  6. Aug 9, 2026Недавно @dmedovich добавил в движок flat inverted index под данные с высокой кардинальност…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →