Go에 새로 추가된 반복자로 인해 많은 사람들이 혼란스러워하는 것 같습니다. 그래서 가능한 한 간단하게 설명하려는 글을 하나 더 작성하기로 결정했습니다.
먼저 Go에서 반복자가 어떻게 호출되고 사용되는지 이해하는 것이 중요하다고 생각합니다. 실제로는 매우 간단합니다. Slices.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") } } }
반복자의 실행 순서와 반복자가 break 및 continue와 같은 다양한 키워드에 어떻게 반응하는지 더 잘 볼 수 있도록 몇 가지 디버그 인쇄 문을 추가했습니다.
마지막으로 구현된 반복자를 사용해 보겠습니다.
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
이 예는 반복자가 어떻게 작동하고 실행되는지를 잘 보여줍니다. 범위 루프에서 반복자를 사용할 때 루프 블록의 모든 명령은 일종의 "yield"라는 함수로 "이동"됩니다. 우리가 Yield를 호출할 때 우리는 본질적으로 이 반복에 대해 다음 값을 사용하여 루프 블록에 있는 모든 것을 실행하도록 go 런타임에 요청합니다. 이것이 루프 본문이 차단되면 Yield가 차단되는 이유이기도 합니다. 런타임에서 이 루프 반복이 중지되어야 한다고 판단하는 경우 Yield는 false를 반환합니다. 루프 블록 실행 중에 break 키워드가 충족될 때 이런 일이 발생할 수 있습니다. 그런 일이 발생하면 더 이상 Yield를 호출해서는 안 됩니다. 그렇지 않으면 계속해서 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