Lekcje modułu (3/5)
Generyki (Go 1.18+)
Od wersji Go 1.18 (marzec 2022 r.) dostępne są parametry typów: funkcje i typy można parametryzować według typów. Umożliwiają pisanie Map, Filter, Set[T], LinkedList[T] bez duplikowania kodu i bez asercji typu interface{} + środowiska wykonawczego.
Funkcja ogólna: składnia
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}Trzy nowe elementy:
[T, U any]po nazwie: deklaruje parametry typu wraz z ich ograniczeniami.any= ograniczenie akceptujące dowolny typ (aliasinterface{}od wersji 1.18).- Wnioskowanie: wywołując
Map([]int{...}, ...)nie musisz wpisywaćMap[int, int](...): kompilator wnioskujeT=int, U=int.
Czasami wnioskowanie nie wystarczy (np. konstruktory bez argumentów T) i konieczna jest jawna instancja: New[*Server]().
Wbudowane ograniczenia
| Ograniczenie | Znaczenie |
|---|---|
| KODEF0 | Dowolny typ (== interface{}). |
| KODEF2 | Typy z == i !=: numeryczne, string, bool, wskaźniki, kanały, porównywalne tablice/struktury. |
comparable pozwala na równość, ale nie < lub >.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
if v == x { return i }
}
return -1
}Dla </> potrzebujesz niestandardowego ograniczenia.
Ograniczenie niestandardowe: wpisz związki
Ograniczeniem jest interfejs z klauzulami „element typu”:
type Ordered interface {
~int | ~int64 | ~float64 | ~string
}
func Min[T Ordered](a, b T) T {
if a < b { return a }
return b
}|= unia: akceptuje dowolny z wymienionych typów.~int= „int lub dowolny typ zdefiniowany jakotype X int” (z tym samym typem bazowym). Bez~, tylko dokładnyint.
Ograniczenie może również mieszać metody i elementy typu:
type Stringable interface {
~string
Len() int
}Typy ogólne
Nie tylko funkcje: struktury i interfejsy mogą mieć również parametry typu.
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")Metody NIE MOGĄ dodawać parametrów typu poza typami: func (s *Set[T]) MapTo[U any](...) nie kompiluje (zamierzone ograniczenie projektu).
Kiedy używać nazw generycznych
Tak gdy:
- Logika jest naprawdę identyczna dla kilku typów (kolekcji, algorytmów, pomocników, takich jak
Map/Filter/Reduce). - Alternatywą byłoby powielenie kodu lub użycie asercji typu
interface{}+ (utrata bezpieczeństwa typu).
Nie gdy:
- Wystarczający i bardziej czytelny jest interfejs z jedną lub dwiema metodami (
io.Reader,fmt.Stringer). - Parametryzujesz „ponieważ możesz”: pojedynczy przypadek użycia → utwórz jego konkretną wersję.
Ćwiczenia
Definisci la funzione rodzajowy Min[T Ordered](a, b T) T che ritorna il minore usando l'operatore <. Il constraint Ordered jest dostępny.
Rozwiązanie dostępne po 3 próbach
Zaimplementuj Map[T, U any](s []T, f func(T) U) []U: aplikacja dla każdego elementu i przywrócenie nowego plasterka.
Rozwiązanie dostępne po 3 próbach
Wbudowane ograniczenie jakości pozwala operatorowi == ma NON operatora <?
func Index[T ???](s []T, x T) int { ... }