前の記事で、最近、ジェネリックについて詳しく説明しているリポジトリgo-generics-the-hard-wayを見つけました。簡単に読んだ後、私はようやく複数の型のスライスをソートすることができました。(私は本当に苦手です!)
理論的基礎#
1. 型のコレクションを定義することで、ジェネリックの制約を設定できます。
// Numericは、任意の数値型で満たされる型制約を表します。
type Numeric interface {
uint | uint8 | uint16 | uint32 | uint64 |
int | int8 | int16 | int32 | int64 |
float32 | float64 |
complex64 | complex128
}
// Sumは、提供された引数の合計を返します。
func Sum[T Numeric](args ...T) T {
var sum T
for i := 0; i < len(args); i++ {
sum += args[i]
}
return sum
}
2. チルダの接頭辞は、制約内の基本型と互換性のある他の型をサポートすることを示します。 上記の内容を前提として、以下のコードを書きます:
// idはint64の新しい型定義です
type id int64
func main() {
fmt.Println(Sum([]id{1, 2, 3}...))
}
コンパイルエラーが発生します:
id does not implement Numeric (possibly missing ~ for int64 in constraint Numeric)
型コレクションのint64
の部分に~
の接頭辞を追加する必要があります。
3. ジェネリックが含まれる場合、ジェネリックシンボルは関数レシーバに含まれている必要があります。 以下のようなジェネリック型がある場合:
// Ledgerは、識別可能な財務記録です。
type Ledger[T ~string, K Numeric] struct {
// IDは、財務記録を識別します。
ID T
// Amountsは、この財務記録に関連付けられた金額のリストです。
Amounts []K
// SumFnは、この財務記録の金額を合計するために使用できる関数です。
SumFn SumFn[K]
}
メソッドを定義する際には、関数レシーバの部分にジェネリックシンボルを明示的に含める必要があります:
// PrintIDAndSumは、財務記録のIDと金額の合計を1行でstdoutに出力します。
func (l Ledger[T, K]) PrintIDAndSum() {
fmt.Printf("%sの合計は%vです\n", l.ID, l.SumFn(l.Amounts...))
}
ソートの実装#
上記の知識を理解した後、sort.Interface
を一括実装することは非常に簡単です。以下はすべてのコードです:
package sort
type comparable interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64 |
~string
}
// Sortableは、sort.Interfaceを実装するジェネリックススライスです。
type Sortable[T comparable] []T
func (s Sortable[T]) Len() int {
return len(s)
}
func (s Sortable[T]) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s Sortable[T]) Less(i, j int) bool {
return s[i] < s[j]
}
各種切片をソートする場合は、単純にSortable
でラップするだけです:
package main
import (
"fmt"
"sort"
gsort "github.com/amtoaer/generic-sort"
)
func main() {
intSlice := []int{1, 3, 2, 4}
stringSlice := []string{"h", "e", "l", "l", "o"}
byteSlice := []byte{'h', 'e', 'l', 'l', 'o'}
sort.Sort(gsort.Sortable[int](intSlice))
sort.Sort(gsort.Sortable[string](stringSlice))
sort.Sort(gsort.Sortable[byte](byteSlice))
fmt.Println(intSlice) // [1 2 3 4]
fmt.Println(stringSlice) // [e h l l o]
fmt.Println(byteSlice) // [101 104 108 108 111]
}