Heim >Backend-Entwicklung >Golang >Generics in Go: Transformieren der Wiederverwendbarkeit von Code

Generics in Go: Transformieren der Wiederverwendbarkeit von Code

Patricia Arquette
Patricia ArquetteOriginal
2025-01-08 06:20:41494Durchsuche

Generika, eingeführt in Go 1.18, haben die Art und Weise, wiederverwendbaren und typsicheren Code zu schreiben, revolutioniert. Generika bringen Flexibilität und Leistung und wahren gleichzeitig Gos Philosophie der Einfachheit. Um jedoch die Nuancen, Vorteile und den Vergleich von Generika mit herkömmlichen Ansätzen (wie interface{} ) zu verstehen, ist ein genauerer Blick erforderlich.

Lassen Sie uns die Feinheiten von Generika erkunden, uns mit Einschränkungen befassen, Generika mit Schnittstellen vergleichen{} und ihre praktischen Anwendungen demonstrieren. Wir werden auch auf Leistungsüberlegungen und Auswirkungen auf die Binärgröße eingehen. Lasst uns eintauchen!

Was sind Generika?

Generika ermöglichen es Entwicklern, Funktionen und Datenstrukturen zu schreiben, die auf jedem Typ ausgeführt werden können und gleichzeitig die Typsicherheit wahren. Anstatt sich auf interface{} zu verlassen, das Typzusicherungen zur Laufzeit beinhaltet, können Sie mit Generics eine Reihe von Einschränkungen angeben, die die zulässigen Operationen für die Typen vorgeben.

Syntax

func FunctionName[T TypeConstraint](parameterName T) ReturnType {
    // Function body using T
}

T: Ein Typparameter, der einen Platzhalter für den Typ darstellt.

TypeConstraint: Beschränkt den Typ von T auf einen bestimmten Typ oder eine Reihe von Typen.

parameterName T: Der Parameter verwendet den generischen Typ T.

ReturnType: Die Funktion kann auch einen Wert vom Typ T.

zurückgeben

Beispiel

func Sum[T int | float64](a, b T) T {
    return a + b
}

func Sum: Deklariert den Namen der Funktion, Sum

[T int | float64]: Gibt eine Typparameterliste an, die T als Typparameter einführt, beschränkt auf bestimmte Typen (int oder float64). Die Summenfunktion kann nur Parameter entweder int oder float64 annehmen, nicht in Kombination, beide müssen entweder int oder float64 sein. Wir werden dies in den folgenden Abschnitten näher untersuchen.

(a, b T): Deklariert zwei Parameter, a und b, beide vom Typ T (der generische Typ). ).

T: Gibt den Rückgabetyp der Funktion an, der dem Typparameter T.

entspricht

Einschränkungen: Bausteine ​​von Generics

Einschränkungen definieren, welche Operationen für einen generischen Typ gültig sind. Go bietet leistungsstarke Tools für Einschränkungen, einschließlich des Pakets für experimentelle Einschränkungen (golang.org/x/exp/constraints).

Integrierte Einschränkungen

Go hat integrierte Einschränkungen mit Generika eingeführt, um Typsicherheit zu gewährleisten und gleichzeitig Flexibilität bei der Definition von wiederverwendbarem und generischem Code zu ermöglichen. Diese Einschränkungen ermöglichen es Entwicklern, Regeln für die Typen durchzusetzen, die in generischen Funktionen oder Typen verwendet werden.

Go verfügt über die folgenden integrierten Einschränkungen

  1. any: Stellt einen beliebigen Typ dar. Es ist ein Alias ​​für interface{}. Dies wird verwendet, wenn keine Einschränkungen erforderlich sind
func FunctionName[T TypeConstraint](parameterName T) ReturnType {
    // Function body using T
}
  1. vergleichbar: Ermöglicht Typen, die Gleichheitsvergleiche unterstützen (== und !=). Nützlich für Kartenschlüssel, Duplikaterkennung oder Gleichheitsprüfungen. Dies kann nicht für Karten, Slices und Funktionen verwendet werden, da diese Typen keinen direkten Vergleich unterstützen.
func Sum[T int | float64](a, b T) T {
    return a + b
}

Experimentelle Einschränkungen

  1. constraints.Complex: Erlaubt komplexe numerische Typen (complex64 und complex128).
  2. constraints.Float: Erlaubt numerische Float-Typen (float32 und float64)
  3. constraints.Integer: Erlaubt jede Ganzzahl mit und ohne Vorzeichen (int8, int16, int32, int64, int, uint8, uint16, uint32, uint64 und uint)
  4. constraints.Signed: Erlaubt jede vorzeichenbehaftete Ganzzahl (int8, int16, int32, int64 und int)
  5. constraints.Unsigned: Erlaubt jede vorzeichenlose Ganzzahl (uint8, uint16, uint32, uint64 und uint).
  6. constraint.Ordered: Erlaubt Typen, die einen Vergleich ermöglichen (<. <=, >, >=), alle numerischen Typen und Zeichenfolgen werden unterstützt (int, float64, string usw.).
func PrintValues[T any](values []T) {
    for _, v := range values {
        fmt.Println(v)
    }
}

Benutzerdefinierte Einschränkungen

Benutzerdefinierte Einschränkungen sind Schnittstellen, die eine Reihe von Typen oder Typverhalten definieren, die ein generischer Typparameter erfüllen muss. Indem wir Ihre eigenen Einschränkungen erstellen, können wir;

  • Typen auf eine bestimmte Teilmenge beschränken, z. B. numerische Typen.

  • Erfordern Typen, um bestimmte Methoden oder Verhaltensweisen zu implementieren.

  • Fügen Sie Ihren generischen Funktionen und Typen mehr Kontrolle und Spezifität hinzu.

Syntax

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
}

Beispiel

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]
}

Summenfunktion kann nur mit den Parametern int, int64 und float64 aufgerufen werden.

Einschränkungen nach Methode

Wenn Sie erzwingen möchten, dass ein Typ bestimmte Methoden implementieren muss, können Sie ihn mithilfe dieser Methoden definieren.

type Numeric interface {
    int | float64 | uint
}

Die Einschränkung Formatter erfordert, dass jeder Typ, der als T verwendet wird, über eine Formatmethode verfügen muss, die ein string.

Einschränkungen kombinieren

Benutzerdefinierte Einschränkungen können Typsätze und Methodenanforderungen kombinieren


type Number interface {
    int | int64 | float64
}

func Sum[T Number](a, b T) T {
    return a + b
}
Diese Einschränkung umfasst beide spezifischen Typen (

int, float54) und erfordert das Vorhandensein einer abs-Methode.

Generics vs. Schnittstelle{}

Vor der Einführung von Generika wurde die Schnittstelle{} verwendet, um Flexibilität zu erreichen. Dieser Ansatz weist jedoch Einschränkungen auf.

Typensicherheit

  • Schnittstelle{}: Verlässt sich auf Laufzeittypzusicherungen, was die Wahrscheinlichkeit von Fehlern zur Laufzeit erhöht.

  • Generika: Bietet Typsicherheit zur Kompilierungszeit und erkennt Fehler frühzeitig während der Entwicklung.

Leistung

  • Schnittstelle{}: Langsamer aufgrund zusätzlicher Laufzeittypprüfungen.

  • Generika: Schneller, da der Compiler optimierte Codepfade speziell für Typen generiert.

Lesbarkeit des Codes

  • Schnittstelle{}: Oft ausführlich und weniger intuitiv, was die Wartung des Codes erschwert.

  • Generika: Eine sauberere Syntax führt zu intuitiverem und wartbarerem Code.

Binäre Größe

  • Schnittstelle{}: Ergibt kleinere Binärdateien, da kein Code für verschiedene Typen dupliziert wird.

  • Generika: Erhöht die Binärgröße aufgrund der Typspezialisierung leicht für eine bessere Leistung.

Beispiel

func FunctionName[T TypeConstraint](parameterName T) ReturnType {
    // Function body using T
}

Code funktioniert gut, die Typzusicherung ist Overhead. Die Add-Funktion kann mit jedem Argument aufgerufen werden. Sowohl a- als auch b-Parameter können von unterschiedlichem Typ sein, allerdings stürzt der Code zur Laufzeit ab.

func Sum[T int | float64](a, b T) T {
    return a + b
}

Generika eliminieren das Risiko von Laufzeitpaniken, die durch falsche Typzusicherungen verursacht werden, und verbessern die Klarheit.

Leistung

Generika erzeugen für jeden Typ speziellen Code, was zu einer besseren Laufzeitleistung im Vergleich zur Schnittstelle{} führt.

Binäre Größe

Es gibt einen Kompromiss: Generika erhöhen die Binärgröße aufgrund der Codeduplizierung für jeden Typ, aber dies ist im Vergleich zu den Vorteilen oft vernachlässigbar.

Einschränkungen von Go Generics

Komplexität in Einschränkungen: Während Einschränkungen wie Constraints.Ordered häufige Anwendungsfälle vereinfachen, kann die Definition stark angepasster Einschränkungen ausführlich werden.

Keine Typinferenz in Strukturen: Im Gegensatz zu Funktionen müssen Sie den Typparameter für Strukturen explizit angeben.

func PrintValues[T any](values []T) {
    for _, v := range values {
        fmt.Println(v)
    }
}

Beschränkt auf Einschränkungen zur Kompilierungszeit: Go-Generika konzentrieren sich auf die Sicherheit zur Kompilierungszeit, während Sprachen wie Rust stärkere Einschränkungen mithilfe von Lebensdauern und Merkmalen bieten.

Lassen Sie uns Benchmarking durchführen – Besser gemacht als gesagt

Wir werden eine einfache Warteschlange sowohl mit Schnittstelle{} als auch generisch implementieren und die Ergebnisse einem Benchmarking unterziehen.

Interface{}-Warteschlangenimplementierung

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
}

Generische Warteschlangenimplementierung

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
}

Analyse der Ergebnisse

  • Ausführungszeit:
    Die generische Implementierung ist etwa 63,64 % schneller als die Interface{}-Version, da sie Laufzeittypzusicherungen vermeidet und direkt mit dem angegebenen Typ arbeitet.

  • Zuweisungen:
    Die interface{}-Version nimmt dreimal mehr Zuweisungen vor, hauptsächlich aufgrund des Ein-/Auspackens beim Einfügen und Abrufen von Werten. Dies erhöht den Aufwand für die Speicherbereinigung.

Bei größeren Arbeitslasten, z. B. 1 Million Enqueue-/Dequeue-Vorgängen, vergrößert sich die Leistungslücke. Reale Anwendungen mit hohen Durchsatzanforderungen (z. B. Nachrichtenwarteschlangen, Jobplaner) profitieren erheblich von Generika.

Letzte Gedanken

Generika in Go schaffen ein Gleichgewicht zwischen Leistung und Einfachheit und bieten eine praktische Lösung zum Schreiben von wiederverwendbarem und typsicherem Code. Obwohl es nicht so funktionsreich ist wie Rust oder C, passt es perfekt zur minimalistischen Philosophie von Go. Das Verstehen von Einschränkungen wie Constraints.Ordered und die effektive Nutzung von Generika können die Codequalität und Wartbarkeit erheblich verbessern.

Da sich Generika ständig weiterentwickeln, werden sie eine zentrale Rolle im Ökosystem von Go spielen. Also tauchen Sie ein, experimentieren Sie und begrüßen Sie die neue Ära der Typsicherheit und Flexibilität in der Go-Programmierung!

Schauen Sie sich das Github-Repository an, um einige Beispiele zu Generika zu finden.

GitHub logo sadananddodawadakar / GoGenerics

Das Repository enthält funktionierende Beispiele für Go-Generika

Go Generics: Umfassendes Beispiel-Repository

Willkommen im Go Generics Repository! Dieses Repository ist eine zentrale Ressource zum Verstehen, Erlernen und Beherrschen von Generika in Go, eingeführt in Version 1.18. Generics bringen die Leistungsfähigkeit von Typparametern in Go und ermöglichen es Entwicklern, wiederverwendbaren und typsicheren Code zu schreiben, ohne Kompromisse bei der Leistung oder Lesbarkeit einzugehen.

Dieses Repository enthält sorgfältig kuratierte Beispiele, die ein breites Themenspektrum abdecken, von grundlegender Syntax bis hin zu fortgeschrittenen Mustern und praktischen Anwendungsfällen. Egal, ob Sie Anfänger oder erfahrener Go-Entwickler sind, diese Sammlung hilft Ihnen dabei, Generika effektiv in Ihren Projekten zu nutzen.


? Was ist drin

? Grundlegende generische Programme

Diese Beispiele stellen die grundlegenden Konzepte von Generika vor und helfen Ihnen, die Syntax und Kernfunktionen zu verstehen:

  1. GenericMap: Demonstriert eine generische Kartenfunktion zum Transformieren von Slices jeglicher Art.
  2. Swap: Ein einfaches, aber wirkungsvolles Beispiel für den generischen Austausch zweier Werte.
  3. FilterSlice: Zeigt, wie gefiltert wird…


Auf GitHub ansehen


Das obige ist der detaillierte Inhalt vonGenerics in Go: Transformieren der Wiederverwendbarkeit von Code. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn