Дисклеймер
Данная статья — проба пера.
Данная статья — перевод/вольная интерпретация соответствующей страницы с Go Wiki. Если знаете английский язык, то, возможно, стоит зайти в первоисточник, а здесь посмотреть лишь примеры.
В данной статье будет речь только о простых одноуровневых циклах.
План
Что такое range func?
Range func — это функция-итератор, которую можно использовать в for-range цикле. Функция позволяет проходиться по какому-либо множеству данных, конечному или бесконечному.
Примером конечного набора данных является какой-либо абстрактный контейнер: массив, слайс, хэш-таблица, структура (если мы хотим пройтись по полям этой структуры), текстовый файл (хотим пройтись построчно) и т.д.
Примером бесконечного набора можно считать какой-либо генератор: генератор рандомных чисел, рандомных строк.
Как с этим дела сейчас?
For-range циклы на данный момент позволяют следующее:
-
Последовательно пройтись по слайсу/массиву
for i, v := range []int{2, 4, 6} {...}
-
В случайном порядке пройтись по мапе
for k, v := range map[string]int{“two”: 2, “four”: 4, “six”: 6} {...}
-
Обновление Go 1.22 (не экспериментально): пройтись по последовательности целых чисел [0, n)
for i := range 10 {...}
На этом все. Если вам нужно итерироваться как-то иначе, то используйте обычный for цикл. Различные библиотеки решают этот вопрос по-разному:
reflect
package main import ( "fmt" "reflect" ) func main() { type S struct { F0 string `alias:"field_0"` F1 string `alias:""` F2 string } s := S{} st := reflect.TypeOf(s) for i := 0; i < st.NumField(); i++ { field := st.Field(i) if alias, ok := field.Tag.Lookup("alias"); ok { if alias == "" { fmt.Println("(blank)") } else { fmt.Println(alias) } } else { fmt.Println("(not specified)") } } }
Итерируемся по полям структуры S.
bufio
package main import ( "bufio" "fmt" "log" "os" ) func main() { file, err := os.Open("README.md") if err != nil { log.Fatal(err) } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { fmt.Println(scanner.Text()) } if err := scanner.Err(); err != nil { log.Fatal(err) } }
Итерируемся по каждой строчке файла «README.md».
В целом, эти решения имеют право на жизнь. Они довольно просты и читаемы.
Задача: проитерироваться по слайсу с конца
Решение 1
s := []int{2, 4, 6} for i := len(s)-1; i >= 0; i-- { // ... }
Ничего сложного нет. Но может быть кому-то покажется не очевидно, или по крайней мере не сразу. Скорее всего программа содержит что-то еще помимо цикла, и бывает иногда сложно помнить все и схватывать все налету.
После Решения 1, Вы решили, что было круто вообще все слайсы проходить с конца. В итоге, нужно поменять все текущие циклы, и быть готовым в будущем копипастить этот длинный for с присваиваниями, получением длины слайса, какими-то проверками. Либо можно сделать так:
Решение 2
type Iter[E any] func(body func(index int, value E)) func Backward[S ~[]E, E any](s S) Iter[E] { return func(body func(int, E)) { for i := len(s) - 1; i >= 0; i-- { body(i, s[i]) } } } // Использование backwardIter := Backward([]int{2, 4, 6}) backwardIter(func(index int, value int) { fmt.Printf("[%d]=%d\n", index, value) })
Прежде чем разбираться с функцией Backward
и типом Iter
, давайте посмотрим на использование. Мы создаем некую переменную backwardIter
, а затем вызываем ее, передавая в качестве аргумента анонимную функцию, которую позволяет нам воспользоваться переменными index
и value
.
Это очень похоже на for-range цикл для слайса. Цикл так же дает нам доступ к индексу и значению текущего элемента для использования в теле.
Возвращаемся к Iter
. Тип Iter
— это функция, в которую передается некая callback-функция body. Это значит, что объект типа Iter
возможно вызовет body с необходимыми аргументами (типа int
и E
соответственно). А может и не вызовет, зависит от содержимого объекта.
Backward
— это функция, которая создает объект типа Iter
. Создаваемый объект может обратиться к слайсу s
. Поэтому может по нему проитерироваться в обратном порядке, запоминая индекс i
, по индексу узнавая значение s[i]
, и вызывая с этими аргументами «тело цикла» body.
Но является ли это решением? Для некоторых задач — да. На самом деле зависит от аргумента, с которым вызывается объект типа Iter
. Мы не можем сказать, что это эквивалент для for-range цикла (с проходом с конца слайса, а не сначала), потому что мы забыли такую конструкцию как defer
. Если мы используем defer
внутри for-range цикла, то defer
выполнится после завершения внешней функции, содержащей цикл. Добавим defer
в «тело цикла» нашего аналога.
backwardIter(func(index int, value int) { defer func() { fmt.Printf("deferred [%d]=%d\n", index, value)}() fmt.Printf("[%d]=%d\n", index, value) })
Когда выполнится defer
? defer
выполнится после того, как именно анонимная функция завершит свою работу. Не функция, которая содержит backwardIter
, а именно анонимная. Кажется, что это является основной причиной, по которой разработчики решили нам подарить итераторы.
Используем range func, пакет iter
Использование совсем простое, то, которое мы привыкли видеть, достаточно лишь создать написать функцию-итератор.
for range h {...} // h has type func(func() bool) for v := range f { ... } // f has type Seq[V], v has type V for k, v := range g { ... } // g has type Seq2[K,V], k and v have types K and V
Семейство типов Почему всего 3? Потому что в языке отсутствуют for-range циклы с 3+ параметрами. Давайте рассмотрим типы Мы знаем, что итератор — это функция. Что будет, если мы вызовем эту функцию? Вывод: Посмотрим на вывод с приставкой «range iterator». Что из него можно понять? Несмотря на то, что итератор — обертка над телом цикла, выражения с И посмотрим на вывод с приставкой «iterator(body)». Он отличается порядком следования элементов. На каждой итерации вызывается Поэтому, если есть желание использовать итераторы, их нужно использовать только в for-range цикле для корректной работы. Можно посмотреть из примера 1. Работает как обычно с for-range циклами. defer исполнится после того, как итератор прекратит работу. Итератор же завершит свою работу, когда тело цикла скажет Вывод: Вначале происходит все, что до цикла. Итератор начинает работу. Итератор заканчивает работу и выполняются Вывод Пусть мы итерируемся как обычно, без итератора. Что произойдет, если в теле цикла произойдет паника? Тогда вся функция, содержащая этот цикл прекратит работу, а так же прекратит работу функция, вызвавшая эту, и т.д. по стеку вызовов, до тех пор, пока стек не кончится или кто-то не использует Поскольку итератор — это обертка для тела цикла, внутри него мы можем использовать Вывод: Как вы думаете, насколько это ожидаемое поведение от for-range цикла? Допустим, что Вы используется сторонний итератор и можете не догадываться о том, что итератор обрабатывает панику. Тогда, вероятнее всего, программа будет некорректной. Разработчики Go пишут, что такое поведение, возможно, — ошибка. А значит в будущих версиях языка поведение может измениться. В примерах выше, мы наблюдаем “Push”-семантику: итератор насильно “запихивает” свои значения внутрь функции, представляющей собой тело цикла. Можно ли их “брать” самостоятельно? Ответ: можем, используя вспомогательные функции Вывод: В данном случае мы получим 5 значений. При этом блока с Давайте изменим итератор так, чтобы он мог вернуть только 4 значения: Вывод: Для того, чтобы попробовать эту экспериментальную фичу и позапускать свои приложения, необходимо выставить следующую переменную среды окружения: Если мозолят глаза красные надписи и подчеркивания при работе с пакетом iter, то: Ctrl+Alt+s Go -> Build tags В поле Custom tags добавить Вывод программы: Прелесть такого итератора заключается в том, что он построен на generic’е. А значит будет работать со слайсами любого типа. Возможно, показанные в этой статье примеры не разбудили в Вас интерес. Поэтому вот примеры от создателей языка, в которых можно было бы использовать итераторы: Когда вы создаете свой собственный контейнер (например, упорядоченную мапу) «Потоковая» обработка. Вместо того, чтобы накопить какой-то результат в слайсе и пройтись по нему, с помощью итераторов, вы сможете сразу получать интересующие значения. Возможно, в стандартной библиотеки появятся аналогичные функции, возвращающие итератор, вместо слайса с результатом: Создатели языка говорят, что программы с использование итераторов могут быть читаемы. Я вхожу в число людей, которым бы больше понравилось видеть Пишите свои примеры использования итераторов, или почему Вы бы не хотели их использовать. P.S.: Жду критики.Seq
(предполагаю от //type Seq0 func(yield func() bool) type Seq[V any] func(yield func(V) bool) type Seq2[K, V any] func(yield func(K, V) bool)
Seq
поподробнее. Это generic функции, которые принимает в себя другую функцию yield.yield
— это callback, в котором содержится тело цикла. Аргументами служат значения, которые «итератор выдает». Seq[int]
должен выдавать int
, Seq[string, int]
соответственно string
и int
. Внутри итератора происходит многократный (хотя можно и 1 раз, но зачем тогда это все?) вызов функции yield
. При этом можно заметить, что yield
возвращает bool
. Зачем? Все дело в том, что в теле цикла могут находиться управляющие конструкции по типу continue
и break
. А значит итератор должен будет каким-нибудь образом узнать, когда ему нужно перестать вызывать тело цикла. Соответственно, если yield(...) == false
(в теле цикла написали break
), то итератор прекращает работу. Неправильный использование итератора
Пример 1. Вызов итератора как функции, defer’ы внутри тела цикла
package main import ( "fmt" "iter" ) func main() { arr := []int{16, 21, 3, 4, 58, 0, 73, 8, 2, 10} backwardIterator := iter.Seq2[int, int](func(yield func(int, int) bool) { for i := len(arr) - 1; i >= 0; i-- { v := arr[i] if !yield(i, v) { return } } }) body := func(i int, v int) bool { if i%2 == 0 { return true } if i == 1 { return false } defer func() { fmt.Printf("iterator(body): [%d]=%d\n", i, v) }() return true } backwardIterator(body) for i, v := range backwardIterator { if i%2 == 0 { continue } if i == 1 { break } defer func() { fmt.Printf("range iterator: [%d]=%d\n", i, v) }() } }
iterator(body): [9]=10 iterator(body): [7]=8 iterator(body): [5]=0 iterator(body): [3]=4 range iterator: [3]=4 range iterator: [5]=0 range iterator: [7]=8 range iterator: [9]=10
defer
выполняются именно после прекращения работы внешней функции, которая и содержит цикл. Это поведение к которому мы привыкли, и которое есть сейчас без функций-итераторов.body
, и defer
выражение выполняется сразу после того, как body
завершает работу. Нюансы
defer внутри тела цикла
defer внутри итератора
break
или когда значений для “выдачи” не останется.Пример 2. defer внутри итератора
package main import ( "fmt" "iter" ) func main() { arr := []int{16, 21, 3, 4, 58, 0, 73, 8, 2, 10} backwardIterator := iter.Seq2[int, int](func(yield func(int, int) bool) { fmt.Println("iterator start") defer func() { fmt.Println("iterator end") }() for i := len(arr) - 1; i >= 0; i-- { v := arr[i] if !yield(i, v) { return } } }) fmt.Println("before for-range") for i, v := range backwardIterator { if i%2 == 0 { continue } if i == 1 { break } fmt.Printf("range iterator: [%d]=%d\n", i, v) } fmt.Println("after for-range") }
before for-range iterator start range iterator: [9]=10 range iterator: [7]=8 range iterator: [5]=0 range iterator: [3]=4 iterator end after for-range
Порядок defer’ов внутри тела цикла и итератора
defer'ы
, отложенные итератором. Происходит все, что после цикла. Выполняются defer'ы
, отложенные телом цикла.Пример 3. Порядок defer’ов
package main import ( "fmt" "iter" ) func main() { arr := []int{16, 21, 3, 4, 58, 0, 73, 8, 2, 10} backwardIterator := iter.Seq2[int, int](func(yield func(int, int) bool) { fmt.Printf("iterator start\n") defer func() { fmt.Printf("iterator end\n") }() for i := len(arr) - 1; i >= 0; i-- { v := arr[i] if !yield(i, v) { return } } }) fmt.Println("before range") for i, v := range backwardIterator { defer func() { fmt.Printf("[%d]=%d\n", i, v) }() } fmt.Println("after range") }
before range iterator start iterator end after range [0]=16 [1]=21 [2]=3 [3]=4 [4]=58 [5]=0 [6]=73 [7]=8 [8]=2 [9]=10
recover внутри итератора
recover()
.recover()
, например, продолжив цикл после паники:Пример 4. recover внутри итератора
package main import ( "fmt" "iter" ) func main() { arr := []int{16, 21, 3, 4, 58, 0, 73, 8, 2, 10} backwardIterator := iter.Seq2[int, int](func(yield func(int, int) bool) { for i := len(arr) - 1; i >= 0; i-- { v := arr[i] if func() bool { defer func() { if err := recover(); err != nil { fmt.Printf("recovered [%d] iteration: %s\n", i, err) } }() return !yield(i, v) }() { return } } }) for i, v := range backwardIterator { if i == 1 { panic("i == 1") } fmt.Printf("[%d]=%d\n", i, v) } }
[9]=10 [8]=2 [7]=8 [6]=73 [5]=0 [4]=58 [3]=4 [2]=3 recovered [1] iteration: i == 1 [0]=16
Push/Pull — семантика
Pull
из пакет iter
. Пример 5. Pull — семантика. Генератор рандомных чисел
package main import ( "fmt" "iter" "math/rand" ) func main() { randIntGen := iter.Seq[int](func(yield func(int) bool) { for { if !yield(rand.Int()) { break } } }) nextRandInt, stop := iter.Pull(randIntGen) defer stop() for i := range 5 { v, ok := nextRandInt() if !ok { fmt.Println("no more values in sequence") break } fmt.Printf("[%d] %d\n", i, v) } }
[0] 2509440936367219129 [1] 8002226566324048486 [2] 9111356380098048175 [3] 69841294487450816 [4] 5523863652085536380
break
мы не достигнем: наш генератор всегда готов выдавать новые значения, лишь бы цикл, в котором этот генератор используется не остановился. Пример 6. Pull-семантика. Генератор 4 рандомных чисел
package main import ( "fmt" "iter" "math/rand" ) func main() { randIntGen := iter.Seq[int](func(yield func(int) bool) { for range 4 { if !yield(rand.Int()) { break } } }) nextRandInt, stop := iter.Pull(randIntGen) defer stop() for i := range 5 { v, ok := nextRandInt() if !ok { fmt.Println("no more values in sequence") break } fmt.Printf("[%d] %d\n", i, v) } }
[0] 3076252664958833517 [1] 5245788927748476723 [2] 4408941311063185846 [3] 2059933908849133260 no more values in sequence
Как попробовать?
GOEXPERIMENT=rangefunc
GoLand
goexperiment.rangefunc
Пример: перебор слайса в случайном порядке
Пример 7: перебор слайса в случайном порядке
package main import ( "fmt" "iter" "math/rand" ) // RandomOrderIterator iterates through s in random order returning // index and value of each element. func RandomOrderIterator[S ~[]E, E any](s S) iter.Seq2[int, E] { order := rand.Perm(len(s)) return func(yield func(int, E) bool) { for _, v := range order { if !yield(v, s[v]) { return } } } } func main() { s1 := []int{5, 10, 4, 7, 3} for i, v := range RandomOrderIterator(s1) { fmt.Printf("s1[%d]=%v\n", i, v) } fmt.Println() s2 := []string{"five", "ten", "four", "seven", "three"} for i, v := range RandomOrderIterator(s2) { fmt.Printf("s2[%d]=%v\n", i, v) } fmt.Println() type someStruct struct { i int str string } s3 := []someStruct{ {5, "five"}, {10, "ten"}, {4, "four"}, {7, "seven"}, {3, "three"}, } for i, v := range RandomOrderIterator(s3) { fmt.Printf("s3[%d]=%v\n", i, v) } }
s1[4]=3 s1[3]=7 s1[2]=4 s1[1]=10 s1[0]=5 s2[0]=five s2[1]=ten s2[3]=seven s2[4]=three s2[2]=four s3[3]={7 seven} s3[2]={4 four} s3[4]={3 three} s3[0]={5 five} s3[1]={10 ten}
Послесловие
strings.Split
strings.Fields
regexp.Regexp.FindAll
for range i, v := range Backward(s) {...}
вместо for i := len(s)-1; i >= 0; i-- {...}
. Возможно, кто-то так не считает.
ссылка на оригинал статьи https://habr.com/ru/articles/794564/
Добавить комментарий