ホームページ  >  記事  >  バックエンド開発  >  Go テレーターを理解する

Go テレーターを理解する

王林
王林オリジナル
2024-08-18 06:32:06972ブラウズ

Understanding Go terators

多くの人が 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
}

何が起こるかというと、ループ本体が反復子に渡される yield 関数に「移動」され、同時に continue と Break がそれぞれ true を返すように変換され、break が 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 などのさまざまなキーワードにイテレータがどのように反応するかをよりよく確認できるように、デバッグ print ステートメントをいくつか追加しました。

最後に、実装されたイテレータを使用してみましょう:

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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。