Solución alternativa para métodos genéricos
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 71 de 107.
Go tiene una limitación notable: no se pueden definir métodos con sus propios parámetros de tipo en un struct. Si bien los structs genéricos funcionan de maravilla, no se permite añadir un método que introduzca un nuevo parámetro de tipo.
Este código no compilará:
type Box[T any] struct {
Value T
}
// ERROR: el método no debe tener parámetros de tipo
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}La solución alternativa es utilizar una función genérica independiente en lugar de un método:
type Box[T any] struct {
Value T
}
// Método regular - utiliza el parámetro de tipo de la estructura
func (b Box[T]) Get() T {
return b.Value
}
// Función independiente - puede tener sus propios parámetros de tipo
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// Usar el método
fmt.Println(intBox.Get()) // 42
// Usar la función independiente
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Los métodos en estructuras genéricas aún pueden usar el parámetro de tipo T de la estructura. La restricción solo se aplica a la introducción de parámetros de tipo adicionales en la firma del método.
Cuando necesites esa flexibilidad, una función genérica que toma el struct como su primer argumento logra el mismo resultado.
Desafío
Fácil¡Vamos a construir un conjunto de herramientas de transformación de datos que demuestre la solución alternativa para la limitación de Go en los métodos genéricos! Dado que los métodos no pueden introducir nuevos parámetros de tipo, crearás funciones genéricas independientes que logren la misma flexibilidad.
Organizarás tu código en dos archivos:
wrapper.go: Define tu contenedor genérico y las funciones de transformación.Crea un struct genérico
Wrapper[T any]con un único campoValuede tipoT.Añade un método
GetenWrapper[T]que devuelva el valor envuelto. Este método utiliza el parámetro de tipo del struct, lo cual está permitido.Crea una función genérica independiente
Transform[T, U any](w Wrapper[T], fn func(T) U) Uque aplique la función de transformación al valor del wrapper y devuelva el resultado. Esta función necesita su propio parámetro de tipoUpara el tipo de salida, razón por la cual debe ser una función independiente en lugar de un método.Crea otra función independiente
TransformToString[T any](w Wrapper[T]) stringque convierta el valor envuelto en una cadena utilizandofmt.Sprintf("%v", ...).main.go: Demuestra el patrón de solución alternativa con diferentes transformaciones.Lee un indicador de tipo (
intostring), luego lee un valor. Crea unWrapperdel tipo apropiado.Para una entrada
int: Crea unWrapper[int], luego usaTransformpara duplicar el valor (devolviendo un int), y usaTransformToStringpara obtener una representación en cadena.Para una entrada
string: Crea unWrapper[string], luego usaTransformpara obtener la longitud de la cadena (devolviendo un int), y usaTransformToStringpara obtener la representación en cadena.Imprime los resultados en este formato:
Original: [value] Transformed: [transformed value] As String: [string representation]
Se proporcionarán las siguientes entradas:
- Línea 1: Indicador de tipo (
intostring) - Línea 2: El valor
Por ejemplo, dado:
int
25Tu salida debería ser:
Original: 25
Transformed: 50
As String: 25Y dado:
string
Hello WorldTu salida debería ser:
Original: Hello World
Transformed: 11
As String: Hello WorldLa idea clave es que Transform toma un Wrapper[T] como su primer argumento e introduce un nuevo parámetro de tipo U para el tipo de retorno, algo que no sería posible como un método en el struct. Este patrón te brinda la flexibilidad de las transformaciones genéricas mientras trabajas dentro de las restricciones del sistema de tipos de Go.
Hoja de referencia
Go no permite que los métodos en structs genéricos introduzcan sus propios parámetros de tipo. Los métodos solo pueden utilizar los parámetros de tipo existentes del struct.
Este código no compilará:
type Box[T any] struct {
Value T
}
// ERROR: method must have no type parameters
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}La solución alternativa es utilizar funciones genéricas independientes en lugar de métodos:
type Box[T any] struct {
Value T
}
// Regular method - uses the struct's type parameter
func (b Box[T]) Get() T {
return b.Value
}
// Standalone function - can have its own type parameters
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// Use the method
fmt.Println(intBox.Get()) // 42
// Use the standalone function
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Los métodos en structs genéricos pueden usar el parámetro de tipo del struct. La restricción solo se aplica a la introducción de parámetros de tipo adicionales en la firma del método. Cuando necesites esa flexibilidad, utiliza una función genérica que reciba el struct como su primer argumento.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer el indicador de tipo
typeIndicator, _ := reader.ReadString('\n')
typeIndicator = strings.TrimSpace(typeIndicator)
// Leer el valor
value, _ := reader.ReadString('\n')
value = strings.TrimSpace(value)
if typeIndicator == "int" {
// Analizar el valor entero
num, _ := strconv.Atoi(value)
// TODO: Crear un Wrapper[int] con el número analizado
// TODO: Usar Transform para duplicar el valor (pista: pasar una función que duplique)
// TODO: Usar TransformToString para obtener la representación en cadena
// TODO: Imprimir los resultados en el formato requerido:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = num // Elimina esta línea cuando uses num
} else if typeIndicator == "string" {
// TODO: Crear un Wrapper[string] con el valor
// TODO: Usar Transform para obtener la longitud de la cadena (pista: pasar una función que devuelva len())
// TODO: Usar TransformToString para obtener la representación en cadena
// TODO: Imprimir los resultados en el formato requerido:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = value // Elimina esta línea cuando uses value
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool