Vai al contenuto
eLearner.app
Modulo 10 · Lezione 3 di 548/50 nel corso~15 min
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

Go
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 di interface{} da Go 1.18).
  • Inferenza: chiamando Map([]int{...}, ...) non devi scrivere Map[int, int](...): il compilatore deduce T=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

ConstraintSignificato
anyQualsiasi tipo (== interface{}).
comparableTipi con == e !=: numerici, string, bool, puntatori, channel, array/struct comparabili.

comparable consente uguaglianza ma non < o >.

Go
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":

Go
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 con type X int" (con underlying type uguale). Senza ~, solo int esatto.

Una constraint può anche mescolare metodi e type element:

Go
type Stringable interface {
    ~string
    Len() int
}

Tipi generici

Non solo funzioni: anche struct e interfacce possono avere type parameter.

Go
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

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

Esercizio#go.m10.l3.e1
Tentativi: 0Caricamento…

Definisci la funzione generica Min[T Ordered](a, b T) T che ritorna il minore usando l'operatore <. Il constraint Ordered è già fornito.

Caricamento editor…

Soluzione disponibile dopo 3 tentativi

Esercizio#go.m10.l3.e2
Tentativi: 0Caricamento…

Implementa Map[T, U any](s []T, f func(T) U) []U: applica f a ogni elemento e restituisce il nuovo slice.

Caricamento editor…

Soluzione disponibile dopo 3 tentativi

Quiz#go.m10.l3.e3
Pronto

Quale constraint built-in consente l'operatore == ma NON l'operatore <?

Go
func Index[T ???](s []T, x T) int { ... }
Opzioni di risposta