いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-400 (Range-Over Functions)(Go 1.22ではまだexperiment扱い, rangefunc)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

Go 1.22では、まだ Experiment な機能となっている Range-Over Functions について、分かりやすい解説記事があったので忘れないうちにメモメモ。。。

www.ardanlabs.com

正式機能になったときには、標準ライブラリに iter パッケージが追加されるみたいですね。

ソースは以下で見れます。

https://go.dev/src/iter/iter.go

以下、ちょっと試してみたメモ。

今回 iter.Pull の方には手を出していません。

サンプル

iter パッケージには、以下のtypeが定義されています。

  • type Seq[V any] func(yield func(V) bool)
  • type Seq2[K, V any] func(yield func(K, V) bool)

一つ目がインデックス無しで、もう一つがインデックス付きですね。

自前の構造体や関数を使って、forループを回したい(つまり、イテレーターを作りたい)場合は、上記のどちらかを戻り値で返す関数を定義すれば良いということになります。

例えば、こんな感じ。

package main

import (
    "fmt"
    "iter"
)

func myIter() iter.Seq[int] {
    ret := func(yield func(int) bool) {
        for i := range 10 {
            if !yield(i) {
                return
            }
        }
    }

    return ret
}

func main() {
    for v := range myIter() {
        fmt.Println(v)
    }
}

myIter関数の戻り値が iter.Seq[int] つまり func(func(int) bool) になっています。

こうすれば、for v := range myIter() で回せるようになります。

で、途中で if !yield(i) { ってやっている部分があります。ここで false だと return するようにしているので、ループがそこで停止します。

この yield の部分は、「ループのBody部分」となります。つまり、上のサンプルだと fmt.Println(v) です。fmt.Printlnはfalseを返すことがないので、結果的にこのループは全部回りきって終わります。

試してみましょう。Go 1.22 時点では、Range-Over Functionsは実験扱いなので、環境変数 GOEXPERIMENT=rangefunc の指定が必要です。

$ GOEXPERIMENT=rangefunc go run main.go
0
1
2
3
4
5
6
7
8
9

ちゃんとループが回りました。先程の話で、ループのBody部分でfalseが返ったらループが止まるか確認してみます。

こんな感じにしてみる。

package main

import (
    "fmt"
    "iter"
)

func myIter() iter.Seq[int] {
    ret := func(yield func(int) bool) {
        for i := range 10 {
            if !yield(i) {
                return
            }
        }
    }

    return ret
}

func myPrint(v int) bool {
    if v > 2 {
        return false
    }

    if _, err := fmt.Println(v); err != nil {
        return false
    }

    return true
}

func main() {
    for v := range myIter() {
        myPrint(v)
    }
}

無理やり感がすごいですが、値が2より大きくなったらfalseを返すようにしています。

試してみましょう。

$ GOEXPERIMENT=rangefunc go run main.go
0
1
2

ちゃんと止まりました。

また、予想通りですがループで break すると、yield関数の戻り値はfalseとなります。

ループの部分を

func main() {
    for v := range myIter() {
        myPrint(v)
        break
    }
}

って形に変えて、実行すると

$ GOEXPERIMENT=rangefunc go run main.go
0

すぐに止まります。

参考情報

https://pkg.go.dev/io@go1.22.2#MultiWriter

https://pkg.go.dev/hash/crc32@go1.22.2#NewIEEE

https://pkg.go.dev/compress/gzip@go1.22.2#Writer

https://pkg.go.dev/encoding/hex@go1.22.2#Dumper

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。