Lecții din modul (3/5)
Generic (Go 1.18+)
Începând cu Go 1.18 (martie 2022), parametrii de tip sunt disponibili: funcțiile și tipurile pot fi parametrizate peste tipuri. Vă permit să scrieți Map, Filter, Set[T], LinkedList[T] fără a duplica cod și fără interface{} + aserțiuni de tip runtime.
Funcția generică: sintaxa
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}Trei elemente noi:
[T, U any]după nume: declară parametrii de tip cu constrângerile lor.any= constrângere care acceptă orice tip (alias deinterface{}începând cu Go 1.18).- Inferență: apelând
Map([]int{...}, ...)nu trebuie să scriețiMap[int, int](...): compilatorul deduceT=int, U=int.
Uneori inferența nu este suficientă (de exemplu, constructori fără argumente T) și este necesară instanțierea explicită: New[*Server]().
Constrângeri încorporate
| Constrângere | Înțeles |
|---|---|
any | Orice tip (== interface{}). |
comparable | Tipuri cu == și !=: numeric, șir, bool, pointeri, canale, matrice/structuri comparabile. |
comparable permite egalitatea, dar nu < sau >.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
if v == x { return i }
}
return -1
}Pentru </> aveți nevoie de o constrângere personalizată.
Constrângere personalizată: uniuni de tip
O constrângere este o interfață cu clauze „element de tip”:
type Ordered interface {
~int | ~int64 | ~float64 | ~string
}
func Min[T Ordered](a, b T) T {
if a < b { return a }
return b
}|= unire: acceptă oricare dintre tipurile enumerate.~int= „int sau orice tip definit catype X int” (cu același tip de bază). Fără~, doar exactint.
O constrângere poate, de asemenea, amesteca metode și elemente de tip:
type Stringable interface {
~string
Len() int
}Tipuri generice
Nu numai funcții: structurile și interfețele pot avea și parametri de tip.
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")Metodele NU POATE adăuga parametri de tip în afara celor de tip: func (s *Set[T]) MapTo[U any](...) nu se compilează (limitare intenționată de proiectare).
Când să folosiți generice
Da când:
- Logica este cu adevărat identică pentru mai multe tipuri (colecții, algoritmi, ajutoare precum
Map/Filter/Reduce). - Alternativa ar fi duplicarea codului sau utilizarea
interface{}+ aserțiuni de tip (pierderea siguranței tipului).
Nu când:
- O interfață cu una sau două metode este suficientă și mai lizibilă (
io.Reader,fmt.Stringer). - Parametrizați „pentru că puteți”: un singur caz de utilizare → faceți o versiune concretă a acestuia.
Exerciții
Definisci la funcție generica Min[T Ordered](a, b T) T che ritorna il minore usando l'operatore <. Il constraint Ordered è già furnizor.
Soluție disponibilă după 3 încercări
Implementa Map[T, U any](s []T, f function(T) U) []U: aplica f a every element and return the new slice.
Soluție disponibilă după 3 încercări
Cale constraint built-in accept l'operatore == ma NON l'operatore <?
func Index[T ???](s []T, x T) int { ... }