golang.org/x/slices を使ってみる

## 皆さん、slices package 使っていますか??

Go は Ver 1.18 より generics に対応しており、それを活かした API が作成されていっています。  
そのような API 群の一つに、 [golang.org/x/slices](https://pkg.go.dev/golang.org/x/exp/slices) があります(※)。  
slices package は、その名の通り、slice 型のデータを取り扱う機能を提供しています。  
筆者も、この package が存在することを知りながらも、実際に試すことが出来ていなかったため、  
自分で試しつつこの記事を書いて情報を読者へ共有したいと思います。

※ `golang.org/x` は Go の準標準のライブラリ/ツールであり、 experimental な機能を提供しています。  
この `x` から後に Go 本体と同梱されている標準 package へ採用されているものもあります。  
> These repositories are part of the Go Project but outside the main Go tree. They are developed under looser compatibility requirements than the Go core.
> これらのリポジトリは Go プロジェクトの一部ですが、メインの Go ツリーの外にあります。これらは Go コアよりも緩やかな互換性要件に基づいて開発されています。
> https://pkg.go.dev/golang.org/x

## Environment

本記事を執筆するにあたり使用した Go の環境は以下です。

```
go 1.20

require golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 // indirect
```

## slices APIs

以下へ slices package のドキュメントを読んで、これは良く使いそうだな、と筆者が考えた API を抜粋して紹介します。

### Min, Max

Min、Max は slice の最小値、最大値をそれぞれ取得します。  
これは筆者も幾度となく自前で実装して(そしてテストも書いて)いたので、活用していきたいところです。  

```go
	// func Min[S ~[]E, E constraints.Ordered](x S) E
	fmt.Println(slices.Min([]int{2, 1, 3})) // 1
	// func Max[S ~[]E, E constraints.Ordered](x S) E
	fmt.Println(slices.Max([]int{2, 1, 3})) // 3
```

### Insert, Delete, Replace

slice への値の挿入、削除、置換です。  
全て範囲指定をすることができるため、使い易くて良いですね。  

```go
	// func Insert[S ~[]E, E any](s S, i int, v ...E) S
	fmt.Println(slices.Insert([]int{2, 1, 3}, 1, []int{4, 5, 6}...)) // [2 4 5 6 1 3]
	// func Delete[S ~[]E, E any](s S, i, j int) S
	fmt.Println(slices.Delete([]int{2, 1, 3}, 1, 2)) // [2 3]
	// func Replace[S ~[]E, E any](s S, i, j int, v ...E) S
	fmt.Println(slices.Replace([]int{2, 1, 3}, 1, 2, 4, 5)) // [2 4 3 3]
```

### Reverse, Sort, Compact

順序の操作を行う API です。  
`Compact` の実行後の期待値は `[1 2 3]` かと思いましたが、そうはなりませんでした。  
API のドキュメントは以下ですが、こちらを読んでもあまり理解ができませんでした。  

> Compact replaces consecutive runs of equal elements with a single copy.  
> This is like the uniq command found on Unix. Compact modifies the contents of the slice s  
> and returns the modified slice, which may have a smaller length.  
> When Compact discards m elements in total, it might not modify the elements  
> s[len(s)-m:len(s)]. If those elements contain pointers you might consider  
> zeroing those elements so that objects they reference can be garbage collected.
. https://pkg.go.dev/golang.org/x/exp/slices#Compact

```go
	s := []int{2, 1, 1, 3, 3}

	// func Reverse[S ~[]E, E any](s S)
	slices.Reverse(s)
	fmt.Println(s) // [3 3 1 1 2]
	// func Sort[S ~[]E, E constraints.Ordered](x S)
	slices.Sort(s)
	fmt.Println(s) // [1 1 2 3 3]
	// func Compact[S ~[]E, E comparable](s S) S
	slices.Compact(s)
	fmt.Println(s) // [1 2 3 3 3]
```

### Clip, Grow

slice の capacity を操作する API です。  
Clip は未使用の capacity を削除し、Grow は指定した数量の capacity を追加します。  

```go
	s := make([]int, 3, 6)

	fmt.Println(cap(s)) // 6

	// func Clip[S ~[]E, E any](s S) S
	fmt.Println(cap(slices.Clip(s))) // 3
	// func Grow[S ~[]E, E any](s S, n int) S
	fmt.Println(cap(slices.Grow(s, 3))) // 6
```

### Clone, Equals

その他、以下のような API がありました。  
`Clone` は slice をコピーし、 `Equals` は slice の値が同じかを評価します。  
Equals は、アドレスや capacity の差は考慮しない仕様のようです。  

```go
	s := []int{2, 1, 3}
	// func Clone[S ~[]E, E any](s S) S
	s2 := slices.Clone(s)
	fmt.Printf("%p %v\n", s, s)   // 0xc000012138 [2 1 3]
	fmt.Printf("%p %v\n", s2, s2) // 0xc000012150 [2 1 3]
	// func Equal[S ~[]E, E comparable](s1, s2 S) bool
	fmt.Println(slices.Equal(s, s2))                      // true
	fmt.Println(slices.Equal(s, slices.Grow(s2, 3)))      // true
	fmt.Println(slices.Equal(s, slices.Delete(s2, 1, 2))) // true
```

## References

参照した文献や、インターネットのページは以下です。

* https://pkg.go.dev/golang.org/x
* https://pkg.go.dev/golang.org/x/exp/slices