首頁 >後端開發 >Golang >理解 Go 迭代器

理解 Go 迭代器

王林
王林原創
2024-08-18 06:32:061070瀏覽

Understanding Go terators

很多人似乎對 Go 中新添加的迭代器感到困惑,這就是為什麼我決定再寫一篇文章試圖以盡可能簡單的方式解釋它們。

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和return 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