Menu
Coddy logo textTech

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.

challenge icon

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 campo Value de tipo T.

    Añade un método Get en Wrapper[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) U que aplique la función de transformación al valor del wrapper y devuelva el resultado. Esta función necesita su propio parámetro de tipo U para 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]) string que convierta el valor envuelto en una cadena utilizando fmt.Sprintf("%v", ...).

  • main.go: Demuestra el patrón de solución alternativa con diferentes transformaciones.

    Lee un indicador de tipo (int o string), luego lee un valor. Crea un Wrapper del tipo apropiado.

    Para una entrada int: Crea un Wrapper[int], luego usa Transform para duplicar el valor (devolviendo un int), y usa TransformToString para obtener una representación en cadena.

    Para una entrada string: Crea un Wrapper[string], luego usa Transform para obtener la longitud de la cadena (devolviendo un int), y usa TransformToString para 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 (int o string)
  • Línea 2: El valor

Por ejemplo, dado:

int
25

Tu salida debería ser:

Original: 25
Transformed: 50
As String: 25

Y dado:

string
Hello World

Tu salida debería ser:

Original: Hello World
Transformed: 11
As String: Hello World

La 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
	}
}
quiz iconPonte a prueba

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