Go 1.18 で導入されたジェネリックは、再利用可能でタイプセーフなコードを記述する方法に革命をもたらしました。ジェネリックは Go のシンプルさの哲学を維持しながら、柔軟性とパワーをもたらします。ただし、ニュアンスや利点、ジェネリックと従来のアプローチ (interface{} など) との比較を理解するには、詳しく調べる必要があります。
ジェネリックの複雑さを調べ、制約を詳しく調べ、ジェネリックとインターフェースを比較して、実際のアプリケーションをデモンストレーションしましょう。{}パフォーマンスに関する考慮事項とバイナリ サイズへの影響についても触れます。飛び込んでみましょう!
開発者は、ジェネリックを使用して、型の安全性を維持しながら任意の型で動作できる関数とデータ構造を作成できます。実行時に型アサーションを必要とするインターフェース{}に依存する代わりに、ジェネリックを使用すると、型に対して許可される操作を指示する一連の制約を指定できます。
構文
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
T: 型のプレースホルダーを表す型パラメーター。
TypeConstraint: T の型を特定の型または型のセットに制限します。
parameterName T: パラメーターはジェネリック型 T を使用します。
ReturnType: この関数は、型 T の値を返すこともできます。
例
func Sum[T int | float64](a, b T) T { return a + b }
func Sum: 関数の名前 Sum
を宣言します。[T int | float64]: 特定の型 (int または float64) に制限された、型パラメーターとして T を導入する型パラメーター リストを指定します。 Sum 関数は、int または float64 のいずれかのパラメーターのみを受け取り、組み合わせることはできません。両方とも int または float64 のいずれかでなければなりません。これについては、以下のセクションでさらに詳しく説明します。
(a, b T): 2 つのパラメーター a と b を宣言します。どちらも型 T (ジェネリック型) ).
T: 型パラメータ T に一致する関数の戻り型を指定します。
制約は、ジェネリック型に対してどのような操作が有効であるかを定義します。 Go は、実験的な制約パッケージ (golang.org/x/exp/constraints) などの制約用の強力なツールを提供します。
Go では、型安全性を提供すると同時に、再利用可能なジェネリック コードを柔軟に定義できるように、ジェネリックを使用した組み込み制約を導入しました。これらの制約により、開発者はジェネリック関数または型で使用される型にルールを適用できます。
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
func Sum[T int | float64](a, b T) T { return a + b }
実験上の制約
func PrintValues[T any](values []T) { for _, v := range values { fmt.Println(v) } }
カスタム制約は、ジェネリック型パラメーターが満たさなければならない型または型の動作のセットを定義するインターフェイスです。独自の制約を作成することで、次のことが可能になります。
数値型など、型を特定のサブセットに制限します。
特定のメソッドまたは動作を実装するには型が必要です。
汎用関数と型にさらに制御性と特異性を追加します。
構文
func CheckDuplicates[T comparable](items []T) []T { seen := make(map[T]bool) duplicates := []T{} for _, item := range items { if seen[item] { duplicates = append(duplicates, item) } else { seen[item] = true } } return duplicates }
例
import ( "golang.org/x/exp/constraints" "fmt" ) func SortSlice[T constraints.Ordered](items []T) []T { sorted := append([]T{}, items...) // Copy slice sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) return sorted } func main() { nums := []int{5, 2, 9, 1} fmt.Println(SortSlice(nums)) // Output: [1 2 5 9] words := []string{"banana", "apple", "cherry"} fmt.Println(SortSlice(words)) // Output: [apple banana cherry] }
Sum 関数 は、int、int64、float64 パラメーターのみを使用して呼び出すことができます。
型に特定のメソッドを実装する必要がある場合は、それらのメソッドを使用して型を定義できます。
type Numeric interface { int | float64 | uint }
Formatter 制約では、T として使用される型には、文字列.
制約の結合
type Number interface { int | int64 | float64 } func Sum[T Number](a, b T) T { return a + b }
int、float54) が含まれており、abs メソッドの存在が必要です。
ジェネリックとインターフェース{}タイプセーフティ
インターフェース{}: 追加のランタイム型チェックにより速度が低下します。
ジェネリック: コンパイラーが型に固有の最適化されたコード パスを生成するため、高速になります。
インターフェース{}: 多くの場合冗長で直感的ではないため、コードの保守が難しくなります。
ジェネリック: よりクリーンな構文により、コードがより直感的で保守しやすくなります。
インターフェース{}: 異なるタイプのコードを複製しないため、バイナリが小さくなります。
ジェネリック: パフォーマンス向上のための型の特殊化により、バイナリ サイズがわずかに増加します。
例
func FunctionName[T TypeConstraint](parameterName T) ReturnType { // Function body using T }
コードは正常に機能しますが、型アサーションはオーバーヘッドです。 Add 関数は任意の引数を指定して呼び出すことができます。a パラメータと b パラメータは両方とも異なる型にすることができますが、コードは実行時にクラッシュします。
func Sum[T int | float64](a, b T) T { return a + b }
ジェネリックにより、不正な型アサーションによって引き起こされる実行時パニックのリスクが排除され、明確さが向上します。
ジェネリックは各タイプに特化したコードを生成するため、インターフェース{}と比較して実行時のパフォーマンスが向上します。
トレードオフが存在します。ジェネリックスでは、型ごとにコードが重複するためバイナリ サイズが増加しますが、これは多くの場合、利点に比べれば無視できるものです。
制約の複雑さ:constraints.Ordered のような制約は一般的な使用例を単純化しますが、高度にカスタマイズされた制約の定義は冗長になる可能性があります。
構造体には型推論がありません: 関数とは異なり、構造体には型パラメーターを明示的に指定する必要があります。
func PrintValues[T any](values []T) { for _, v := range values { fmt.Println(v) } }
コンパイル時の制約に限定: Go ジェネリックスはコンパイル時の安全性に焦点を当てていますが、Rust のような言語はライフタイムとトレイトを使用したより強力な制約を提供します。
インターフェースと汎用の両方を備えたシンプルなキューを実装し、結果をベンチマークします。
func CheckDuplicates[T comparable](items []T) []T { seen := make(map[T]bool) duplicates := []T{} for _, item := range items { if seen[item] { duplicates = append(duplicates, item) } else { seen[item] = true } } return duplicates }
import ( "golang.org/x/exp/constraints" "fmt" ) func SortSlice[T constraints.Ordered](items []T) []T { sorted := append([]T{}, items...) // Copy slice sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) return sorted } func main() { nums := []int{5, 2, 9, 1} fmt.Println(SortSlice(nums)) // Output: [1 2 5 9] words := []string{"banana", "apple", "cherry"} fmt.Println(SortSlice(words)) // Output: [apple banana cherry] }
type Numeric interface { int | float64 | uint }
実行時間:
汎用実装は、実行時の型アサーションを回避し、指定された型で直接動作するため、インターフェイス バージョンよりも約 63.64% 高速です。
割り当て:
インターフェースのバージョンでは、主に値の挿入および取得時のボックス化/ボックス化解除により、3 倍多くの割り当てが行われます。{}これにより、ガベージ コレクションにオーバーヘッドが追加されます。
100 万回のエンキュー/デキュー操作など、より大きなワークロードの場合、パフォーマンスのギャップは拡大します。高スループット要件を持つ現実世界のアプリケーション (メッセージ キュー、ジョブ スケジューラなど) は、ジェネリックから大きな恩恵を受けます。
Go のジェネリックスは、パワーとシンプルさのバランスをとり、再利用可能でタイプセーフなコードを作成するための実用的なソリューションを提供します。 Rust や C ほど機能が豊富ではありませんが、Go のミニマリスト哲学と完全に一致しています。 Constraints.Ordered などの制約を理解し、ジェネリックを効果的に活用することで、コードの品質と保守性を大幅に向上させることができます。
ジェネリックは進化し続けるため、Go のエコシステムで中心的な役割を果たすことになります。 Go プログラミングにおける型安全性と柔軟性の新時代を体験し、実験し、受け入れてください!
ジェネリックに関するいくつかのサンプルについては、github リポジトリをチェックアウトしてください。
Go ジェネリック リポジトリ へようこそ!このリポジトリは、バージョン 1.18 で導入された Go のジェネリックスを理解し、学習し、習得するためのワンストップ リソースです。ジェネリックは Go に型パラメーターの機能をもたらし、開発者がパフォーマンスや読みやすさを犠牲にすることなく、再利用可能で型安全なコードを作成できるようにします。
このリポジトリには、基本的な構文から高度なパターン、実用的な使用例まで、幅広いトピックをカバーする慎重に厳選された例が含まれています。初心者でも経験豊富な Go 開発者でも、このコレクションはプロジェクトでジェネリックを効果的に活用するのに役立ちます。
これらの例では、ジェネリックスの基本的な概念を紹介し、構文とコア機能を理解するのに役立ちます。
以上がGo のジェネリックス: コードの再利用性を変革するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。