Golang の Generics で使いたいものは大体 samber/lo にあった話

# はじめに
本記事は [Go Advent Calendar 2022](https://qiita.com/advent-calendar/2022/go) の18日目の記事です。

Generics を使った関数でやりたかったことが、[samber/lo](https://github.com/samber/lo) に大体揃ってたという話をします。
すでに自前で書いてた関数もいくつかあったのですが、このライブラリを知り置き換えていったという体験談です。

※結果的に「`samber/lo` のなかでこの辺り使えるかも」というものを私の独断と偏見でピックアップする記事になってます。

# `samber/lo` とは
`samber/lo` は [16日目のアベンドカレンダーの記事](https://qiita.com/sakashin10291029/items/bb16c24bea1399353295) でも取り上げられていまして、そこから説明を引用させていただきました。

> samber/loは,多種多様な便利関数を提供するパッケージです。
JavaScriptのライブラリのLodashライクであるとREADMEにも記載があります。
個人的には大好きなパッケージでして,Map,Filter等のスライスを操作する関数をよく使っています。
>
> https://github.com/samber/lo
https://github.com/lodash/lodash

計測時点(2022/12/17)でスター数は驚異の 9.1k! last commit は当日! 今後のメンテやアップデートも期待できるライブラリです。

# Genericsでやりたかったこと
## map の keys/values
あった。

`lo.Keys`: https://pkg.go.dev/github.com/samber/lo#Keys
playgound: https://go.dev/play/p/Uu11fHASqrU

`lo.Values`: https://pkg.go.dev/github.com/samber/lo#Values
playgound: https://go.dev/play/p/nnRTQkzQfF6

## min/max
あった。

`lo.Min`: https://pkg.go.dev/github.com/samber/lo#Min
`lo.Max`: https://pkg.go.dev/github.com/samber/lo#Max

```min_max.go
func main() {
	fmt.Println(lo.Min([]int{9, 8, 2, 1, 4, 5}))
	fmt.Println(lo.Max([]int{9, 8, 2, 1, 4, 5}))
	// Output:
	// 1
	// 9
}
```
(配列じゃなくて可変引数だったら最高だったな)

## Must（エラーだったらpanic）
エラーだったらpanic。そうじゃなかったらerr以外の引数をreturnするもの。
test の前処理とかのpanicでもいい時に使いたくなる。

あった。引数はMust6まで定義されてた。
`lo.Must1`: https://pkg.go.dev/github.com/samber/lo#Must1

GoDoc の playground がわかりづらかったので自分でも書いてみた
```must.go
func main() {
	fmt.Println(lo.Must1(returnNoErr())) // 1を出力
	fmt.Println(lo.Must1(returnErr()))   // panicする
}

func returnErr() (int, error) {
	return 0, errors.New("error")
}

func returnNoErr() (int, error) {
	return 1, nil
}
```

## 値→ポインタ 変換
test の時に超絶欲しくなるやつ。
WebAPI実装時に [swag.String](https://pkg.go.dev/github.com/go-openapi/swag#String), [swag.Int64](https://pkg.go.dev/github.com/go-openapi/swag#Int64) とか書いてた人も多いのではないでしょうか。

あった。

`lo.ToPtr`: https://pkg.go.dev/github.com/samber/lo#ToPtr
`lo.ToSlicePtr`: https://pkg.go.dev/github.com/samber/lo#ToSlicePtr

↓こんな感じ。ValueObjectを導入しているチームも嬉しいはず。
```pointer.go
type ValueObject string
type PointerFieldStruct struct {
	Str *string
	VO  *ValueObject
}

func main() {
	p := PointerFieldStruct{
		Str: lo.ToPtr("foo"),
		VO:  lo.ToPtr(ValueObject("bar")),
	}

	// こう書くことが多かった
	// str:= "foo"
	// vo := ValueObject("bar")
	// p := PointerFieldStruct{
	// 	Str: &str,
	// 	VO:  &vo,
	// }

	fmt.Println(*p.Str)
	fmt.Println(*p.VO)
	// Output:
	// foo
	// bar
}
```

## ポインタ→値 変換（nil時のオプショナル付き）
nilだったら、ゼロ値もしくは指定した値を書くもの。

あった。

`lo.FromPtr`: https://pkg.go.dev/github.com/samber/lo#FromPtr
`lo.FromPtrOr`:https://pkg.go.dev/github.com/samber/lo#FromPtrOr

```from_ptr.go
func main() {
	var str *string

	fmt.Println(lo.FromPtr(str)) // ゼロ値。つまり空文字。
	fmt.Println(lo.FromPtrOr(str, "別の値"))
	// Output:
	// 
	// 別の値
}
```

## slice の重複排除
あった。

`lo.Uniq`: https://pkg.go.dev/github.com/samber/lo#Uniq
playground: https://go.dev/play/p/DTzbeXZ6iEN

## slice を一定数ごとにsplit
IDの一覧だけ取得して、そのあと一定数ごとにループ処理を回したい時に使いたくなったもの。

あった。

`lo.Chunk`: https://pkg.go.dev/github.com/samber/lo#Chunk
playground: https://go.dev/play/p/EeKl0AuTehH

## slice を逆順に
あった。

`lo.Reverse`: https://pkg.go.dev/github.com/samber/lo#Reverse
playground: https://go.dev/play/p/fhUMLvZ7vS6

## slice に一つでも条件に当てはまるものがあったら
早期return したり、continue したり、ループを抜けるためになにかしたりというのが必要な時に欲しかった。
（Generics じゃなくて堅実に書いていきたいという人もいそうな気もする）

あった。

`lo.Contains`: https://pkg.go.dev/github.com/samber/lo#Contains
`lo.ContainsBy`: https://pkg.go.dev/github.com/samber/lo#ContainsBy
`lo.Some`: https://pkg.go.dev/github.com/samber/lo#Some
`lo.SomeBy`: https://pkg.go.dev/github.com/samber/lo#SomeBy

（ContainsByとSomeByの実装同じだった）
```contains.go

type Order struct {
	Name   string
	Status string
}

func ActiveStatus() []string {
	return []string{
		"not_yet",
		"work_in_progress",
	}
}

func main() {
	orders := []Order{
		{"1", "not_yet"},
		{"2", "work_in_progress"},
		{"3", "done"},
		{"4", "canceled"},
	}

	for _, o := range orders {
		if lo.Contains(ActiveStatus(), o.Status) {
			fmt.Println(o)
		}
	}
	// Output:
	// {1 not_yet}
	// {2 work_in_progress}
}
```


```contains_by.go
type Team struct {
	Name    string
	Members []Person
}
type Person struct {
	Name  string
	Cards []int
}

func main() {
	teams := []Team{
		{
			Name: "A",
			Members: []Person{
				{"bubu", []int{1, 2, 3}},
				{"suke", []int{2, 3, 4}},
			},
		},
		{
			Name: "B",
			Members: []Person{
				{"foo", []int{1, 2, 3}},
				{"bar", []int{4, 5, 6}},
			},
		},
		{
			Name: "C",
			Members: []Person{
				{"hoge", []int{7, 8, 9}},
			},
		},
	}

	// 4 のカードを持っているTeamを出力

	const key = 4
	targets := make([]Team, 0, len(teams))

	// ここでつかってます
	for _, team := range teams {
		if lo.ContainsBy(team.Members, func(member Person) bool {
			return lo.Contains(member.Cards, key)
		}) {
			targets = append(targets, team)
		}
	}

	// こう書くことが多かった
	// for _, team := range teams {
	// 	find := false
	// 	for _, member := range team.Members {
	// 		for _, card := range member.Cards {
	// 			if card == key {
	// 				targets = append(targets, team)
	// 				find = true
	// 				break
	// 			}
	// 		}
	// 		if find {
	// 			break
	// 		}
	// 	}
	// }

	for _, t := range targets {
		fmt.Println(t.Name)
	}
	// Output:
	// A
	// B
}
```
※上記のcontains_by.goのサンプルコードだとループのなかで `fmt.Println` すれば良いですが、「N+1問題を避けるために後続で一括処理する」などのケースを想定してもらえるとしっくりくるかなと思います。




## データモデル系
なかった。

queue, set などはなかったので自前実装にしてます。
本記事の内容からは少し外れるのでざっくりどう作ってるかだけ記載します。
（Generics を試してみたい方は queue, set ぜひ実装してみてください！）

queue: [sliceで実装した記事](https://qiita.com/ruiu/items/8edf22cd5fc8f7511687) を参考につくってます。Push, Top, Pop, IsEmpty, Len あたりのメソッドを持たせてます。型定義だけ書いておきます。
```queue.go
type Queue[T any] struct {
	queue []T
}
```

set: `type Set[T comparable] map[T]Unit` を定義して使ってます。setを型定義した方が目的が明確になると思って使ってます。Has, Put, Delete, Merge, ToSlice のメソッドと、SliceToSet という変換関数を書いたりしてます。



# おわりに
あったらいいのが揃ってました。

Generics で書くと楽かも
 → でも車輪の再発明が云々
 → でも再発明かどうかライブラリを探すこと自体が時間かかる云々

みたいな悩みが解消すればと思います。