Sintaxis y uso de parámetros de tipo en Go

Los parámetros de tipo permiten que las funciones y los tipos sean “plantillas” que se especializan según el tipo de dato que reciban. En lugar de usar any (la interfaz vacía) y depender de aserciones de tipo costosas en tiempo de ejecución, los generics permiten que el compilador verifique la seguridad de tipos antes de que el programa se ejecute. La sintaxis utiliza corchetes [] para definir una lista de parámetros de tipo, donde cada parámetro se asocia a una restricción de tipo (type constraint). Esta restricción define el conjunto de tipos permitidos y, lo más importante, qué operaciones (como <, >, +) se pueden realizar sobre ellos.

Si intentas realizar una operación de comparación en una función genérica donde el parámetro de tipo no garantiza dicha capacidad, el compilador fallará inmediatamente. Este mecanismo es fundamental cuando necesitas escribir lógica reutilizable para colecciones (slices, mapas, pilas) sin perder la robustez del tipado estático. Si usas la sintaxis incorrecta o restriñes demasiado los tipos, podrías terminar con código que no es realmente genérico o con errores de compilación difíciles de rastrear en interfaces complejas.

package main

import (
	"cmp"
	"fmt"
)

// Stack es un tipo genérico definido con el parámetro [T any].
// 'any' es una restricción que permite cualquier tipo, pero no permite 
// realizar operaciones como '<' o '>'.
type Stack[T any] struct {
	items []T
}

func (s *Stack[T]) Push(val T) {
	s.items = append(s.items, val)
}

func (s *Stack[T]) Pop() (T, bool) {
	if len(s.items) == 0 {
		var zero T // Obtenemos el valor por defecto del tipo T (ej. 0 para int, "" para string)
		return zero, false
	}
	index := len(s.items) - 1
	val := s.items[index]
	s.items = s.items[:index]
	return val, true
}

// Max usa el parámetro de tipo T con la restricción cmp.Ordered.
// Esto permite usar operadores de comparación como '>' porque cmp.Ordered 
// garantiza que el tipo soporta estas operaciones [disponible desde Go 1.21].
func Max[T cmp.Ordered](a, b T) T {
	if a > b {
		return a
	}
	return b
}

// Map demuestra el uso de múltiples parámetros de tipo [T, U any].
// Transforma una slice de tipo T en una slice de tipo U mediante una función f.
func Map[T, U any](input []T, f func(T) U) []U {
	result := make([]U, len(input))
	for i, v := range input {
		result[i] = f(v)
	}
	return result
}

func main() {
	// 1. Inferencia de tipos: El compilador deduce que T es int por los argumentos.
	m := Max(10, 20)
	fmt.Printf("Max (inferido): %v\n", m)

	// 2. Instanciación explícita: Forzamos el tipo usando corchetes.
	mStr := Max[string]("apple", "zebra")
	fmt.Printf("Max (explícito): %v\n", mStr)

	// 3. Uso de tipos genéricos personalizados.
	s := Stack[int]{}
	s.Push(100)
	s.Push(200)
	val, _ := s.Pop()
	fmt.Printf("Stack Pop: %v\n", val)

	// 4. Múltiples parámetros de tipo con transformación.
	nums := []int{1, 2, 3}
	// Convertimos ints a strings. El compilador infiere T=int y U=string.
	strs := Map(nums, func(n int) string {
		return fmt.Sprintf("ID-%d", n)
	})
	fmt.Printf("Map result: %v\n", strs)
}

En el ejemplo anterior, hemos visto cómo la estructura Stack[T any] utiliza un parámetro de tipo llamado T. Al declarar any como restricción, le decimos al compilador que aceptará cualquier tipo, pero solo podemos usar funciones que no requieran comparaciones (como append o la asignación en la línea s.items = append(...)).

La función Max[T cmp.Ordered] es un caso clave de restricción. Al usar cmp.Ordered, estamos limitando T a tipos que el compilador sabe que se pueden comparar (números, strings). Si intentaras pasar un struct personalizado a Max que no implemente comparaciones, el código ni siquiera compilaría. Observa también la función Max(10, 20) en el main: no escribimos Max[int](10, 20) porque el compilador de Go es capaz de realizar la inferencia de tipos, analizando los argumentos para determinar qué tipo de T debe ser.

Cuando trabajamos con funciones que transforman datos, como Map[T, U any], necesitamos dos parámetros distintos: uno para el tipo de entrada (T) y otro para el de salida (U). Esto es vital en pipelines de datos donde, por ejemplo, quieres convertir una lista de IDs (int) en una lista de objetos de configuración (struct).

El error frecuente

Un error común ocurre cuando intentas usar operadores de comparación en una función que tiene una restricción demasiado laxa, como any.

// ESTO FALLARÁ AL COMPILAR
func EsMayor[T any](a, b T) bool {
    return a > b // Error: invalid operation: a > b (type T does not support operator >)
}

Aunque any permite que la función reciba cualquier cosa, el compilador no puede garantizar que a y b soporten el operador >. Para solucionar esto, debes ser específico con la restricción usando una interfaz que defina ese comportamiento, como cmp.Ordered.

82

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio