>  기사  >  백엔드 개발  >  Go 터레이터 이해하기

Go 터레이터 이해하기

王林
王林원래의
2024-08-18 06:32:061041검색

Understanding Go terators

Go에 새로 추가된 Iterator 때문에 많은 분들이 헷갈려 하시는 것 같아서, 최대한 간단하게 설명하려고 글을 하나 더 작성하게 되었습니다.

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
    //  })
}

위 내용은 Go 터레이터 이해하기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.