Lecciones del módulo (3/5)
Genéricos (Go 1.18+)
Desde Go 1.18 (marzo de 2022), parámetros de tipo están disponibles: las funciones y los tipos se pueden parametrizar sobre los tipos. Le permiten escribir Map, Filter, Set[T], LinkedList[T] sin duplicar código y sin interface{} + afirmaciones de tipo de tiempo de ejecución.
Función genérica: la sintaxis
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}Tres nuevos elementos:
[T, U any]después del nombre: declara los parámetros de tipo con sus restricciones.any= restricción que acepta cualquier tipo (alias deinterface{}desde Go 1.18).- Inferencia: al llamar a
Map([]int{...}, ...)no es necesario escribirMap[int, int](...): el compilador infiereT=int, U=int.
A veces la inferencia no es suficiente (por ejemplo, constructores sin argumentos T) y se necesita una instanciación explícita: New[*Server]().
Restricciones incorporadas
| Restricción | Significado |
|---|---|
| CÓDIGOPH0 | Cualquier tipo (== CÓDIGOPH1). |
| CÓDIGOPH2 | Tipos con == y !=: numérico, cadena, bool, punteros, canales, matrices/estructuras comparables. |
comparable permite la igualdad pero no < o >.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
if v == x { return i }
}
return -1
}Para </> necesita una restricción personalizada.
Restricción personalizada: uniones de tipos
Una restricción es una interfaz con cláusulas de "elemento de tipo":
type Ordered interface {
~int | ~int64 | ~float64 | ~string
}
func Min[T Ordered](a, b T) T {
if a < b { return a }
return b
}|= unión: acepta cualquiera de los tipos enumerados.~int= "int o cualquier tipo definido comotype X int" (con el mismo tipo subyacente). Sin~, sólointexacto.
Una restricción también puede mezclar métodos y elementos de tipo:
type Stringable interface {
~string
Len() int
}Tipos genéricos
No solo funciones: las estructuras y las interfaces también pueden tener parámetros de tipo.
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")Los métodos NO PUEDEN agregar parámetros de tipo más allá de los del tipo: func (s *Set[T]) MapTo[U any](...) no compila (limitación de diseño intencional).
Cuándo usar genéricos
Sí cuando:
- La lógica es verdaderamente idéntica para varios tipos (colecciones, algoritmos, ayudantes como
Map/Filter/Reduce). - La alternativa sería duplicar código o usar afirmaciones de tipo
interface{}+ (perdiendo seguridad de tipo).
No cuando:
- Una interfaz con uno o dos métodos es suficiente y más legible (
io.Reader,fmt.Stringer). - Estás parametrizando "porque puedes": un caso de uso único → haz una versión concreta del mismo.
Ejercicios
Definisci la funzione generica Min[T Ordered](a, b T) T che ritorna il minore usando el operador <. La restricción Ordenada es già fornito.
Solución disponible después de 3 intentos
Implementa Map[T, U any](s []T, f func(T) U) []U: aplica f a cada elemento y restituye el nuevo segmento.
Solución disponible después de 3 intentos
Cual restricción incorporada acepta el operador == ma NON el operador <?
func Index[T ???](s []T, x T) int { ... }