Lektionen des Moduls (3/5)
Generics (Go 1.18+)
Seit Go 1.18 (März 2022) sind Typparameter verfügbar: Funktionen und Typen können über Typen parametrisiert werden. Mit ihnen können Sie Map, Filter, Set[T], LinkedList[T] schreiben, ohne Code zu duplizieren und ohne interface{} + Laufzeittypzusicherungen.
Generische Funktion: die Syntax
func Map[T, U any](s []T, f func(T) U) []U {
out := make([]U, len(s))
for i, v := range s {
out[i] = f(v)
}
return out
}
doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 })
// doubled is []int{2, 4, 6}Drei neue Elemente:
[T, U any]nach dem Namen: deklariert die Typparameter mit ihren Einschränkungen.any= Einschränkung, die jeden Typ akzeptiert (Alias voninterface{}seit Go 1.18).- Inferenz: Beim Aufruf von
Map([]int{...}, ...)müssen Sie nichtMap[int, int](...)schreiben: Der Compiler leitetT=int, U=intab.
Manchmal reicht die Inferenz nicht aus (z. B. Konstruktoren ohne T-Argumente) und eine explizite Instanziierung ist erforderlich: New[*Server]().
Eingebaute Einschränkungen
| Einschränkung | Bedeutung |
|---|---|
any | Beliebiger Typ (== interface{}). |
comparable | Typen mit == und !=: numerisch, String, Bool, Zeiger, Kanäle, vergleichbare Arrays/Strukturen. |
comparable erlaubt Gleichheit, aber nicht < oder >.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
if v == x { return i }
}
return -1
}Für </> benötigen Sie eine benutzerdefinierte Einschränkung.
Benutzerdefinierte Einschränkung: Typvereinigungen
Eine Einschränkung ist eine Schnittstelle mit „Typelement“-Klauseln:
type Ordered interface {
~int | ~int64 | ~float64 | ~string
}
func Min[T Ordered](a, b T) T {
if a < b { return a }
return b
}|= Union: akzeptiert jeden der aufgelisteten Typen.~int= „int oder jeder Typ, der alstype X intdefiniert ist“ (mit demselben zugrunde liegenden Typ). Ohne~, nur exakterint.
Eine Einschränkung kann auch Methoden und Typelemente mischen:
type Stringable interface {
~string
Len() int
}Generische Typen
Nicht nur Funktionen: Strukturen und Schnittstellen können auch Typparameter haben.
type Set[T comparable] struct {
m map[T]struct{}
}
func NewSet[T comparable]() *Set[T] {
return &Set[T]{m: make(map[T]struct{})}
}
func (s *Set[T]) Add(v T) { s.m[v] = struct{}{} }
func (s *Set[T]) Has(v T) bool { _, ok := s.m[v]; return ok }
s := NewSet[string]()
s.Add("ada")Methoden KÖNNEN KEINE Typparameter hinzufügen, die über die des Typs hinausgehen: func (s *Set[T]) MapTo[U any](...) wird nicht kompiliert (absichtliche Designeinschränkung).
Wann sollten Generika verwendet werden?
Ja wenn:
- Die Logik ist für mehrere Typen (Sammlungen, Algorithmen, Helfer wie
Map/Filter/Reduce) wirklich identisch. – Die Alternative wäre das Duplizieren von Code oder die Verwendung voninterface{}+ Typzusicherungen (Verlust der Typsicherheit).
Nein wenn:
- Eine Schnittstelle mit einer oder zwei Methoden reicht aus und ist besser lesbar (
io.Reader,fmt.Stringer). - Sie parametrisieren „weil Sie es können“: einen einzelnen Anwendungsfall → erstellen Sie eine konkrete Version davon.
Übungen
Definieren Sie die generische Funktion Min[T Ordered](a, b T) T, die mit dem Operator < den kleineren Wert ausgibt. Der Zwang, bestellt zu werden, ist vorbei.
Lösung nach 3 Versuchen verfügbar
Implementieren Sie Map[T, U any](s []T, f func(T) U) []U: Wenden Sie es auf ein neues Element an und stellen Sie es wieder her.
Lösung nach 3 Versuchen verfügbar
Eingebaute Qualitätsbeschränkung stimmt dem Bediener == zu, aber der Bediener ist nicht <?
func Index[T ???](s []T, x T) int { ... }