Upgrade to Pro — share decks privately, control downloads, hide ads and more …

利用者視点で考える、イテレータとの上手な付き合い方

syumai
September 24, 2024

 利用者視点で考える、イテレータとの上手な付き合い方

「Go 1.23のイテレータについて知っておくべきこと」を登壇用に再編したものです
https://zenn.dev/syumai/articles/cqud4gab5gv2qkig5vh0

syumai

September 24, 2024
Tweet

More Decks by syumai

Other Decks in Programming

Transcript

  1. 自己紹介 syumai ECMAScript 仕様輪読会 主催 株式会社ベースマキナで管理画面のSaaSを開発中 GoでGraphQLサーバー (gqlgen) や TypeScriptでフロント

    エンドを書いています 最近技術ブログも始めたのでよろしくお願いします: https://tech.basemachina.jp/ Software Design 12月号からCloudflare Workersの連載をして ます Twitter: @__syumai Website: https://syum.ai
  2. イテレータは3種類ある 2, 3のケースで、 V と K には任意の型を使うことができ、rangeループではその型の値を それぞれ受け取る // 1

    var f1 func(func() bool) for range f1 {} // 値が何も返らないので、 x := range f1 の形式では書けない // 2 var f2 func(func(int) bool) for x := range f2 {} // xはint型 // 3 var f3 func(func(string, int) bool) for x, y := range f3 {} // xはstring型、yはint型
  3. iter.Seq / iter.Seq2の追加 2, 3と同じ形式の関数の型が標準ライブラリのiter packageに定義されているので、1のパタ ーン以外は基本的にはこれらを使う package iter //

    2: func(func(V) bool) type Seq[V any] func(yield func(V) bool) // 3: func(func(K, V) bool) type Seq2[K, V any] func(yield func(K, V) bool)
  4. iter.Pull / iter.Pull2の追加 標準のpush型イテレータをpull型のイテレータに変換する 例はiter packageのドキュメントから引用 next, stop := iter.Pull(seq)

    defer stop() for { v1, ok1 := next() if !ok1 { return } v2, ok2 := next() if !yield(v1, v2) { return } if !ok2 { return } } // https://pkg.go.dev/iter#hdr-Pulling_Values
  5. イテレータとsliceの違い Go 1.22までの世界で、最も柔軟なデータ列表現 (channelを除く) だったsliceと比較する 1. メモリ効率 sliceは、保持する要素数分のメモリが最低でも必要 イテレータはただの関数なので、固定のメモリ容量をとられない 利用シーンに応じて、効率的なデータ構造を選択可能

    2. データの終端 sliceには終わりがある 循環リストなどを表現できない イテレータは、終端処理を明示的に行うので、循環リストであっても表現可能 イテレーションを終了せず、無限に値を生成し続けることも可能
  6. イテレータとsliceの違い 1. メモリ効率 → 固定のメモリ容量をとられない 2. データの終端 → 終端処理を明示的に行う →

    イテレータはsliceよりも多くのケースに対応できる、より一般的なデータ列の形式
  7. データ列の変換 string型のデータ列を受け取って、全て大文字に変換して返すイテレータの例 よくある Map 関数の操作 func ToUpper(seq iter.Seq[string]) iter.Seq[string] {

    /* 実装は省略 */ } func main() { s := []string{"a", "b", "c"} for _, v := range s { fmt.Println(v) // a, b, c } for v := range ToUpper(slices.Values(s)) { fmt.Println(v) // A, B, C } } // https://go.dev/play/p/EiA7MJOyntv
  8. データ列の集約 イテレータを受け取った関数が、列挙された値を一つの値に集約する 他の言語で言う Reduce や Fold のイメージ func SumInt(seq iter.Seq[int])

    int { var result int for i := range seq { result += i } return result } func main() { ints := []int{1, 2, 3} sum := SumInt(slices.Values(ints)) fmt.Println(sum) // 6 } // https://go.dev/play/p/A1sPvW9ofoB
  9. データ列の抽出 イテレータを受け取った関数が、列挙された値の一部を抽出する 他の言語で言う Filter のイメージ func FilterEven(seq iter.Seq[int]) iter.Seq[int] {

    /* 実装は省略 */ } func main() { ints := []int{1, 2, 3, 4, 5} for i := range FilterEven(slices.Values(ints)) { fmt.Println(i) // 2, 4 } } // https://go.dev/play/p/h4xzk7RJ_se
  10. go-functionalの利用例 import ( "github.com/BooleanCat/go-functional/v2/it" "github.com/BooleanCat/go-functional/v2/it/filter" "github.com/BooleanCat/go-functional/v2/it/op" ) func main() {

    // 変換 { s := []string{"a", "b", "c"} for v := range it.Map(slices.Values(s), strings.ToUpper) { fmt.Println(v) // A, B, C } } // 集約 { ints := []int{1, 2, 3} sum := it.Fold(slices.Values(ints), op.Add, 0) fmt.Println(sum) // 6 } // 抽出 { ints := []int{1, 2, 3, 4, 5} for i := range it.Filter(slices.Values(ints), filter.IsEven) { fmt.Println(i) // 2, 4 } } } // https://go.dev/play/p/L3lJhX_dnKX
  11. slice / mapとイテレータの相互変換 基本的には以下の通り slice / map -> イテレータ slices

    / maps packageの、 All() / Values() 関数を使う イテレータ -> slice / map slices / maps packageの、 Collect() 関数を使う
  12. sliceとイテレータの相互変換関数一覧 sliceをイテレータに変換するのに使う関数 slices.All() []V のsliceを、 iter.Seq2[int, V] の形式で、index、値の組のイテレータに変 換 slices.Values()

    []V のsliceを、 iter.Seq[V] の形式で、値のみのイテレータに変換 イテレータをsliceに変換するのに使う関数 slices.Collect() iter.Seq[V] のイテレータを、 []V のsliceに変換 slices.Sorted() iter.Seq[V cmp.Ordered] のイテレータを、ソートされた []V のsliceに変換 ソート方法を指定できる slices.SortedFunc() もある
  13. mapとイテレータの相互変換関数一覧 mapをイテレータに変換するのに使う関数 maps.All() map[K]V のmapを、 iter.Seq2[K, V] の形式で、キー、値の組のイテレータに変 換 maps.Keys()

    map[K]V のmapを、 iter.Seq[K] の形式で、キーのみのイテレータに変換 maps.Values() map[K]V のmapを、 iter.Seq[V] の形式で、値のみのイテレータに変換 イテレータをmapに変換するのに使う関数 maps.Collect() iter.Seq2[K, V] のイテレータを、 map[K]V のmapに変換