Slide 1

Slide 1 text

利用者視点で考える、イテレータとの上手な付き合い方 syumai Goのイテレータ深堀りNight (2024/9/24)

Slide 2

Slide 2 text

自己紹介 syumai ECMAScript 仕様輪読会 主催 株式会社ベースマキナで管理画面のSaaSを開発中 GoでGraphQLサーバー (gqlgen) や TypeScriptでフロント エンドを書いています 最近技術ブログも始めたのでよろしくお願いします: https://tech.basemachina.jp/ Software Design 12月号からCloudflare Workersの連載をして ます Twitter: @__syumai Website: https://syum.ai

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

利用者視点とは? ここで言う「利用者」は、 「実装者」との対比 イテレータそのものの実装はせず、利用だけする人 Goのイテレータに関する知識が要求される方の多くが「利用者」に該当するはず

Slide 6

Slide 6 text

「利用者」が知っておくべきことは? 「利用者」としては、次の2つを理解しておくのが重要 イテレータはどうやって使うのか イテレータについてどこまで知っておく必要があるのか → どこから先は知らなくてもいいのか

Slide 7

Slide 7 text

〜ここから前提知識〜

Slide 8

Slide 8 text

言語仕様の変更 for文のrangeループの仕様が変わった Go 1.23から、特定の形式の関数をrange loopの対象にできる

Slide 9

Slide 9 text

イテレータは3種類ある 1. イテレーションごとに値を返さない 2. イテレーションごとに1つの値を返す (channel, 整数に対するrangeループと同じ) 3. イテレーションごとに2つの値を返す (slice, mapなどに対するrangeループと同じ) // 1 func(func() bool) // 2 func(func(V) bool) // 3 func(func(K, V) bool)

Slide 10

Slide 10 text

イテレータは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型

Slide 11

Slide 11 text

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)

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

〜ここまで前提知識〜

Slide 14

Slide 14 text

結局、Goのイテレータとは何か?

Slide 15

Slide 15 text

結局、Goのイテレータとは何か? Goのイテレータは、 「任意のデータ構造を隠蔽してデータ列として扱うための、一般 的な形式」

Slide 16

Slide 16 text

データ構造の隠蔽 Goのイテレータはただの関数なので、どんなデータ構造も隠蔽できる → 任意のデータ構造を対象にrangeループが使えるようにできる 任意のデータ構造の例 二分木 Ordered Map range loopにおいて、sliceやmapを使うのとほぼ変わらない体験を、ライブラリ作者 が提供できるようになる

Slide 17

Slide 17 text

イテレータとsliceの違い Go 1.22までの世界で、最も柔軟なデータ列表現 (channelを除く) だったsliceと比較する 1. メモリ効率 sliceは、保持する要素数分のメモリが最低でも必要 イテレータはただの関数なので、固定のメモリ容量をとられない 利用シーンに応じて、効率的なデータ構造を選択可能 2. データの終端 sliceには終わりがある 循環リストなどを表現できない イテレータは、終端処理を明示的に行うので、循環リストであっても表現可能 イテレーションを終了せず、無限に値を生成し続けることも可能

Slide 18

Slide 18 text

イテレータとsliceの違い 1. メモリ効率 → 固定のメモリ容量をとられない 2. データの終端 → 終端処理を明示的に行う → イテレータはsliceよりも多くのケースに対応できる、より一般的なデータ列の形式

Slide 19

Slide 19 text

イテレータの使い方

Slide 20

Slide 20 text

イテレータの使い方 主に2つの使い方がある 1. rangeループで使う → 説明を省略します 2. イテレータを受け取る関数に渡す イテレータを受け取った関数は、列挙された値に対して「変換・集約・抽出」な どを行う → こちらの例をいくつか紹介

Slide 21

Slide 21 text

データ列の変換 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

Slide 22

Slide 22 text

データ列の集約 イテレータを受け取った関数が、列挙された値を一つの値に集約する 他の言語で言う 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

Slide 23

Slide 23 text

データ列の抽出 イテレータを受け取った関数が、列挙された値の一部を抽出する 他の言語で言う 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

Slide 24

Slide 24 text

go-functional https://github.com/BooleanCat/go-functional Goのイテレータを使って関数型チックにコードを書けるライブラリ ヘルパー関数も充実している v2.0.0が2024/9/8にリリースされた 現在 (2024年9月) の最新はv2.1.0

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

slice / mapとイテレータの相互変換 基本的には以下の通り slice / map -> イテレータ slices / maps packageの、 All() / Values() 関数を使う イテレータ -> slice / map slices / maps packageの、 Collect() 関数を使う

Slide 27

Slide 27 text

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() もある

Slide 28

Slide 28 text

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に変換

Slide 29

Slide 29 text

どこまで知っておく必要があるのか

Slide 30

Slide 30 text

どこまで知っておく必要があるのか 基本的には、以下を知っていればOK イテレータの種類 イテレータの使い方 slice / mapとイテレータの相互変換

Slide 31

Slide 31 text

知らなくていいことは? イテレータの実装方法 知っているのが理想的ではあるが、必須ではない 実は、一般的なWebアプリケーション程度ならイテレータを実装する機会はあま りないはず

Slide 32

Slide 32 text

今から積極的に導入すべきか? (私見ですが)標準ライブラリ、サードパーティライブラリでの導入が進まないとあ まりうまみがない 各ライブラリのイテレーションの形式が揃っていないものを全部自分で変換するの は、手間のわりにうまみが少ない → 現時点では、無理に導入する必要はないと思っています

Slide 33

Slide 33 text

まとめ 利用者としては、必ずしもイテレータの実装方法までは知らなくてよい 言語仕様の変更、イテレータの使い方については知っておくべき まだライブラリの対応が進んでいるところなので、今から無理に導入する必要はない

Slide 34

Slide 34 text

ご清聴ありがとうございました!