ホームページ > バックエンド開発 > Golang > Go のジェネリックス: コードの再利用性を変革する

Go のジェネリックス: コードの再利用性を変革する

Patricia Arquette
リリース: 2025-01-08 06:20:41
オリジナル
494 人が閲覧しました

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 では、型安全性を提供すると同時に、再利用可能なジェネリック コードを柔軟に定義できるように、ジェネリックを使用した組み込み制約を導入しました。これらの制約により、開発者はジェネリック関数または型で使用される型にルールを適用できます。

Go には以下の組み込み制約があります

  1. any: 任意の型を表します。これはインターフェースのエイリアスです。{}これは制約が必要ない場合に使用されます
func FunctionName[T TypeConstraint](parameterName T) ReturnType {
    // Function body using T
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
  1. comparable: 等価比較 (== および !=) をサポートする型を許可します。マップキー、重複検出、または等価性チェックに役立ちます。これらの型は直接比較をサポートしていないため、これはマップ、スライス、関数には使用できません。
func Sum[T int | float64](a, b T) T {
    return a + b
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

実験上の制約

  1. constraints.Complex: 複素数値型 (complex64 および complex128) を許可します。
  2. constraints.Float: float 数値型 (float32 および float64) を許可します
  3. constraints.Integer: 符号付きおよび符号なしの両方の整数を許可します (int8、int16、int32、int64、int、uint8、uint16、uint32、uint64、および uint)
  4. constraints.Signed: 任意の符号付き整数 (int8、int16、int32、int64、および int) を許可します
  5. constraints.Unsigned: 任意の符号なし整数 (uint8、uint16、uint32、uint64、および uint) を許可します。
  6. constraint.Ordered: 比較を許可する型 (<. <=、>、>=)、すべての数値型と文字列がサポートされます (int、float64、string など)。
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
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ジェネリックにより、不正な型アサーションによって引き起こされる実行時パニックのリスクが排除され、明確さが向上します。

パフォーマンス

ジェネリックは各タイプに特化したコードを生成するため、インターフェース{}と比較して実行時のパフォーマンスが向上します。

バイナリサイズ

トレードオフが存在します。ジェネリックスでは、型ごとにコードが重複するためバイナリ サイズが増加しますが、これは多くの場合、利点に比べれば無視できるものです。

Go ジェネリックの制限

制約の複雑さ: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 リポジトリをチェックアウトしてください。

GitHub logo サダナンドダワダカール / ゴージェネリクス

リポジトリには go ジェネリックの動作例が含まれています

Go Generics: 包括的なサンプル リポジトリ

Go ジェネリック リポジトリ へようこそ!このリポジトリは、バージョン 1.18 で導入された Go のジェネリックスを理解し、学習し、習得するためのワンストップ リソースです。ジェネリックは Go に型パラメーターの機能をもたらし、開発者がパフォーマンスや読みやすさを犠牲にすることなく、再利用可能で型安全なコードを作成できるようにします。

このリポジトリには、基本的な構文から高度なパターン、実用的な使用例まで、幅広いトピックをカバーする慎重に厳選された例が含まれています。初心者でも経験豊富な Go 開発者でも、このコレクションはプロジェクトでジェネリックを効果的に活用するのに役立ちます。


?中身は何ですか

?基本的な汎用プログラム

これらの例では、ジェネリックスの基本的な概念を紹介し、構文とコア機能を理解するのに役立ちます。

  1. GenericMap: 任意のタイプのスライスを変換する汎用マップ関数を示します。
  2. Swap: 2 つの値を一般的に交換する、シンプルかつ強力な例です。
  3. FilterSlice: フィルタリングする方法を示します…


GitHub で表示


以上がGo のジェネリックス: コードの再利用性を変革するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート