Lezioni del modulo (3/5)
Generics (Go 1.18+)
Dal Go 1.18 (marzo 2022) sono disponibili i type parameter: funzioni e tipi possono essere parametrizzati su tipi. Permettono di scrivere Map, Filter, Set[T], LinkedList[T] senza duplicare codice e senza interface{} + type assertion runtime.
Funzione generica: la sintassi
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 è []int{2, 4, 6}Tre elementi nuovi:
[T, U any]dopo il nome: dichiara i type parameter con i loro vincoli.any= constraint che accetta qualsiasi tipo (alias diinterface{}da Go 1.18).- Inferenza: chiamando
Map([]int{...}, ...)non devi scrivereMap[int, int](...): il compilatore deduceT=int, U=int.
A volte l'inferenza non basta (es. costruttori senza argomenti T) e l'istanziazione esplicita serve: New[*Server]().
I constraint built-in
| Constraint | Significato |
|---|---|
any | Qualsiasi tipo (== interface{}). |
comparable | Tipi con == e !=: numerici, string, bool, puntatori, channel, array/struct comparabili. |
comparable consente uguaglianza ma non < o >.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
if v == x { return i }
}
return -1
}Per </> serve un constraint custom.
Constraint custom: union di tipi
Un constraint è un'interfaccia con clausole "type element":
type Ordered interface {
~int | ~int64 | ~float64 | ~string
}
func Min[T Ordered](a, b T) T {
if a < b { return a }
return b
}|= unione: ammette uno qualsiasi dei tipi listati.~int= "int o qualsiasi tipo definito contype X int" (con underlying type uguale). Senza~, solointesatto.
Una constraint può anche mescolare metodi e type element:
type Stringable interface {
~string
Len() int
}Tipi generici
Non solo funzioni: anche struct e interfacce possono avere type parameter.
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")I metodi NON possono aggiungere altri type parameter oltre a quelli del tipo: func (s *Set[T]) MapTo[U any](...) non compila (limitazione voluta del design).
Quando usare i generics
Sì quando:
- La logica è davvero identica per più tipi (collezioni, algoritmi, helper come
Map/Filter/Reduce). - L'alternativa sarebbe duplicare codice o usare
interface{}+ type assertion (perdendo type safety).
No quando:
- Un'interfaccia con uno o due metodi basta ed è più leggibile (
io.Reader,fmt.Stringer). - Stai parametrizzando "perché si può": un solo caso d'uso → fanne una versione concreta.
Esercizi
Definisci la funzione generica Min[T Ordered](a, b T) T che ritorna il minore usando l'operatore <. Il constraint Ordered è già fornito.
Soluzione disponibile dopo 3 tentativi
Implementa Map[T, U any](s []T, f func(T) U) []U: applica f a ogni elemento e restituisce il nuovo slice.
Soluzione disponibile dopo 3 tentativi
Quale constraint built-in consente l'operatore == ma NON l'operatore <?
func Index[T ???](s []T, x T) int { ... }