DMarlon Studio

Lección 8 · 8 de 20

Genéricos: parámetros de tipo y restricciones · Go

Escriba funciones genéricas en Go con parámetros de tipo y restricciones: un Sum para varios tipos numéricos y un Map para cualquier tipo.

Go · Genéricos: parámetros de tipo y restricciones en DMarlon Studio: el código del ejemplo y su salida en la terminal

Desde Go 1.18, una función puede tener parámetros de tipo, entre corchetes antes de los normales: Sum[T Number](values []T) T. T es un tipo que se decide al llamar, y la misma función suma enteros, decimales y céntimos sin escribirla tres veces ni recurrir a any.

Number es una restricción: una interfaz que, en lugar de métodos, enumera los tipos permitidos con |. Gracias a ella, el compilador sabe que todos admiten += y deja escribir total += v. La tilde de `~int64` significa «int64 o cualquier tipo cuyo tipo base sea int64», y por eso entra también Cents.

var total T crea el valor cero de T: 0 para los números. Al llamar, Go deduce T de los argumentos: con []int{3, 5, 8}, T es int. Con nil no hay nada de lo que deducirlo, así que se indica a mano: Sum[float64](nil) devuelve 0.

Map[T, U any] tiene dos parámetros de tipo y la restricción `any`, que admite cualquier tipo pero no deja hacer con él casi nada: solo guardarlo, pasarlo y devolverlo. Basta para aplicar f a cada elemento: de []int a []string ([#7 #12 #30], y %T confirma []string) y de ahí a []int con las longitudes.

Para comparar valores con == existe la restricción `comparable`. En Go 1.19 la biblioteca estándar no trae una para < y >; puede declararla usted como Number, con los tipos que necesite.

Código · main.go

package mainimport "fmt"// Number is a constraint: the set of types that T may be.type Number interface{ ~int | ~int64 | ~float64 }type Cents int64 // ~int64 also lets in named types such as Centsfunc Sum[T Number](values []T) T {	var total T // the zero value of T	for _, v := range values {		total += v	}	return total}// Map works for any input type T and any output type U.func Map[T, U any](items []T, f func(T) U) []U {	out := make([]U, 0, len(items))	for _, item := range items {		out = append(out, f(item))	}	return out}func main() {	fmt.Println(Sum([]int{3, 5, 8}), Sum([]float64{2.5, 1.25}), Sum([]Cents{1999, 450}))	fmt.Println(Sum[float64](nil)) // explicit type argument	labels := Map([]int{7, 12, 30}, func(n int) string { return fmt.Sprintf("#%d", n) })	fmt.Printf("%v %T\n", labels, labels)	fmt.Println(Map(labels, func(s string) int { return len(s) }))}

Salida · go run main.go

16 3.75 2449
0
[#7 #12 #30] []string
[2 3 3]

Para recordar

  • Los parámetros de tipo van entre corchetes: func Sum[T Number].
  • Una restricción es una interfaz que dice qué tipos valen.
  • ~int64 admite también los tipos definidos sobre int64.
  • Go deduce T de los argumentos; si no puede, se escribe Sum[float64].
  • any admite cualquier tipo; comparable, los que se comparan con ==.

Ejercicio

Escriba Filter[T any](items []T, keep func(T) bool) []T y úsela para quedarse con los importes de []Cents{1999, 450, 12000} mayores de 1000. Después sume el resultado con Sum.