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

GoのGenericsによるslice操作との付き合い方

Avatar for syumai syumai
June 18, 2025

 GoのGenericsによるslice操作との付き合い方

golang.tokyo #39 の発表資料です
https://golangtokyo.connpass.com/event/353988/

Avatar for syumai

syumai

June 18, 2025
Tweet

More Decks by syumai

Other Decks in Programming

Transcript

  1. 自己紹介 syumai ECMAScript 仕様輪読会 / Asakusa.go 主催 株式会社ベースマキナで管理画面のSaaS を開発中 Go

    でGraphQL サーバー (gqlgen) や TypeScript でフロント エンドを書いています Software Design 2023 年12 月号から2025 年2 月号まで Cloudflare Workers の連載をしました Twitter ( 現𝕏): @__syumai Website: https://syum.ai
  2. basemachina/lo Generics を使って Map , Filter などの操作をslice に対して簡単に行えるライブラリ 型情報を使ったslice 以外の操作も必要に応じて追加している

    numbers := []int{1, 2, 3, 4, 5} doubled := lo.Map(numbers, func(x int) int { return x * 2 }) fmt.Println(doubled) // [2, 4, 6, 8, 10] evens := lo.Filter(numbers, func(x int) bool { return x%2 == 0 }) fmt.Println(evens) // [2, 4] fmt.Printf("%T\n", lo.ToPtr(1)) // *int
  3. 現在使える機能 ( 全19 種類) * Array/Slice操作 - フィルタリング - Filter

    - FilterWithIndex - 変換 - Map - MapWithError - MapWithIndex - MapWithIndexError - FlatMap - FlatMapWithIndex - 検索・判定 - Find - Every - Some - HasDuplicates - HasDuplicatesBy - 集約 - Reduce - ReduceWithIndex - 集合演算 - Intersect * Map操作 - Invert * ポインタ操作 - ToPtr - FromPtrOr
  4. 1. 従来のslice 操作の課題 range を使った詰め替えでは、うっかり事故る可能性がある lint を入れて防ぐしかない ( 私見) lo.Map

    や lo.Filter などの関数を使ったコードの方が読むのが楽 行いたい操作が関数名から明確 slice 操作の実装の詳細を確認しなくていい
  5. basemachina/lo のslice 変換機能の使い方 DB から取ってきたデータを変換してGraphQL resolver から返す処理 func (r *queryResolver)

    Views(ctx context.Context) ([]*gql.View, error) { // []*model.View を取得 views, err := loader.ListViews(ctx, r.project.ID) if err != nil { return nil, err } // []*gql.View を返却 return lo.Map(views, gqlpresenter.View), nil }
  6. basemachina/lo のslice 変換機能の使い方 basemachina/lo 無しの場合 func (r *queryResolver) Views(ctx context.Context)

    ([]*gql.View, error) { // []*model.View を取得 views, err := loader.ListViews(ctx, r.project.ID) if err != nil { return nil, err } // []*gql.View を返却 gqlViews := make([]*gql.View, len(views)) for i, v := range views { gqlViews[i] = gqlpresenter.View(v) } return gqlViews }
  7. basemachina/lo のslice 変換機能の使い方 変換用のコードは使い回せるので別package で実装 lo.Map のシグニチャを変えて (item T, index

    int) を (item T) だけにして いるのでこれでOK package gqlpresenter func View(v *model.View) *gql.View { return &gql.View{ ID: v.ID, Name: v.Name, Code: v.Code, } }
  8. もし lo.Map のシグニチャが iteratee (item T, index int) だったら 変換処理側で合わせる場合

    package gqlpresenter // 毎回 `_ int` を書かないといけない func View(v *model.View, _ int) *gql.View { return &gql.View{ ID: v.ID, Name: v.Name, Code: v.Code, } }
  9. もし lo.Map のシグニチャが iteratee (item T, index int) だったら 変換処理を使う側で合わせる場合

    func (r *queryResolver) Views(ctx context.Context) ([]*gql.View, error) { views, err := loader.ListViews(ctx, r.project.ID) if err != nil { return nil, err } // 毎回無名関数を書く return lo.Map(views, func (v *model.View, _ int) *gql.View { return gqlpresenter.View(v) }), nil }
  10. basemachina/lo のslice 変換機能の使い方 変換時にエラーが発生しうる場合は lo.MapWithError を使う func (r *queryResolver) Actions(ctx

    context.Context) ([]*gql.Action, error) { /* ... */ return lo.MapWithError(actions, gqlpresenter.Action) }
  11. basemachina/lo のslice 変換機能の使い方 MapWithIndexError (MapWithError の内部で呼ぶ) の実装 https://github.com/samber/lo/pull/43 で提案 func

    MapWithIndexError[T any, R any]( collection []T, iteratee func(item T, index int) (R, error)) ([]R, error) { result := make([]R, len(collection)) var err error for i, item := range collection { result[i], err = iteratee(item, i) if err != nil { return nil, err } } return result, nil }