Многих людей, кажется, смущают новые итераторы, добавленные в Go, поэтому я решил написать еще одну статью, пытаясь объяснить их как можно проще.
Во-первых, я думаю, важно понять, как итераторы вообще вызываются и используются в Go, и это на самом деле довольно просто, давайте воспользуемся итератором срезов.All в качестве примера. Вот как вы обычно используете этот итератор:
package main import ( "fmt" "slices" ) func main() { slice := []string{ "Element 1", "Element 2", "Element 3", "Element 4", } for index, element := range slices.All(slice) { if index >= 2 { break } fmt.Println(index, element) } // Output: // 0 Element 1 // 1 Element 2 }
А вот как это выглядит на самом деле:
package main import ( "fmt" "slices" ) func main() { slice := []string{ "Element 1", "Element 2", "Element 3", "Element 4", } slices.All(slice)(func (index int, element string) bool { if index >= 2 { return false // break } fmt.Println(index, element) return true // continue loop as normal }) // Output: // 0 Element 1 // 1 Element 2 }
Происходит следующее: тело цикла «перемещается» в функцию доходности, которая передается итератору, в то время как continue и Break преобразуются, чтобы возвращать true и false соответственно. return true также добавляется в конец цикла, чтобы сигнализировать о том, что мы хотели бы получить следующий элемент, если до этого ничто другое не приняло другое решение.
Это не совсем точное описание того, что делает компилятор, и я не проверял реализацию Go, чтобы проверить это, но, судя по моим наблюдениям, они дают эквивалентные результаты.
Теперь, когда вы понимаете, как они вызываются, и понимаете, насколько это просто на самом деле, вам будет намного проще понять, как создать собственный итератор и его выполнение.
Давайте создадим итератор отладки, который будет печатать отладочные сообщения для каждого шага реализации итератора, который будет проходить по всем элементам в срезе (функция Slices.All).
Сначала я создам небольшую вспомогательную функцию для выхода из сообщения с текущим временем выполнения.
import ( "fmt" "time" ) var START time.Time = time.Now() func logt(message string) { fmt.Println(time.Since(START), message) }
Вернуться к итератору:
import ( "iter" ) func DebugIter[E any](slice []E) iter.Seq2[int, E] { logt("DebugIter called") // the same way iter.All returned function // we called in order to iterate over slice // here we are returning a function to // iterate over all slice elements too return func(yield func(int, E) bool) { logt("Seq2 return function called, starting loop") for index, element := range slice { logt("in loop, calling yield") shouldContinue := yield(index, element) if !shouldContinue { logt("in loop, yield returned false") return } logt("in loop, yield returned true") } } }
Я добавил несколько операторов отладочной печати, чтобы мы могли лучше видеть порядок выполнения итератора и то, как он будет реагировать на различные ключевые слова, такие как перерыв и продолжение.
Наконец, давайте воспользуемся реализованным итератором:
func main() { slice := []string{ "Element 1", "Element 2", "Element 3", "Element 4", } for index, element := range DebugIter(slice) { message := "got element in range of iter: " element logt(message) if index >= 2 { break } if index > 0 { continue } time.Sleep(2 * time.Second) logt("ended sleep in range of iter") } }
Отдаст нам результат:
11.125µs DebugIter called 39.292µs Seq2 return function called, starting loop 42.459µs in loop, calling yield 44.292µs got element in range of iter: Element 1 2.001194292s ended sleep in range of iter 2.001280459s in loop, yield returned true 2.001283917s in loop, calling yield 2.001287042s got element in range of iter: Element 2 2.001291084s in loop, yield returned true 2.001293125s in loop, calling yield 2.0012955s got element in range of iter: Element 3 2.001297542s in loop, yield returned false
Этот пример хорошо показывает, как работают и выполняются итераторы. При использовании итератора в цикле диапазона все инструкции в блоке цикла как бы «перемещаются» в функцию, которая называется выход. Когда мы вызываем выход, мы, по сути, просим среду выполнения go выполнить все, что находится в блоке цикла со следующим значением для этой итерации, поэтому выход будет заблокирован, если тело цикла будет заблокировано. Если среда выполнения определяет, что эта итерация цикла должна быть остановлена, выход возвращает false. Это может произойти, когда ключевое слово Break встречается во время выполнения блока цикла, мы не должны больше вызывать выход, если это произойдет. В противном случае нам следует продолжать вызывать метод yield.
Полный код:
package main import ( "fmt" "time" "iter" ) var START time.Time = time.Now() func logt(message string) { fmt.Println(time.Since(START), message) } func DebugIter[E any](slice []E) iter.Seq2[int, E] { logt("DebugIter called") // the same way iter.All returned function // we called in order to iterate over slice // here we are returning a function to // iterate over all slice elements too return func(yield func(int, E) bool) { logt("Seq2 return function called, starting loop") for index, element := range slice { logt("in loop, calling yield for") shouldContinue := yield(index, element) if !shouldContinue { logt("in loop, yield returned false") return } logt("in loop, yield returned true") } } } func main() { slice := []string{ "Element 1", "Element 2", "Element 3", "Element 4", } for index, element := range DebugIter(slice) { message := "got element in range of iter: " element logt(message) if index >= 2 { break } if index > 0 { continue } time.Sleep(2 * time.Second) logt("ended sleep in range of iter") } // unfold compiler magic // DebugIter(slice)(func (index int, element string) bool { // message := "got element in range of iter: " element // logt(message) // if index >= 2 { // return false // } // if index > 0 { // return true // } // time.Sleep(2 * time.Second) // logt("ended sleep in range of iter") // // return true // }) }
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3