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 puedes definir métodos con sus propios parámetros de tipo en una estructura. Aunque las estructuras genéricas funcionan muy bien, no se permite añadir un método que introduzca un parámetro de tipo nuevo.

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 usar una función genérica independiente en lugar de un método:

type Box[T any] struct {
    Value T
}

// Método regular: usa el parámetro de tipo de la struct
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 de las estructuras genéricas aún pueden usar el parámetro de tipo de la estructura T. La restricción solo se aplica a introducir parámetros de tipo adicionales en la firma del método.

Cuando necesitas esa flexibilidad, una función genérica que toma la estructura como su primer argumento logra el mismo resultado.

challenge icon

Desafío

Fácil

¡Construyamos un kit de herramientas de transformación de datos que demuestre la solución alternativa para la limitación de Go con los métodos genéricos! Como los métodos no pueden introducir nuevos parámetros de tipo, crearás funciones genéricas independientes que logran 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 una estructura genérica Wrapper[T any] con un único campo Value de tipo T.

    Añade un método Get a Wrapper[T] que devuelva el valor envuelto. Este método utiliza el parámetro de tipo de la estructura, 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 contenedor y devuelva el resultado. Esta función necesita su propio parámetro de tipo U para el tipo de salida, por lo que 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 usando fmt.Sprintf("%v", ...).

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

    Lee un indicador de tipo (int o string) y después lee un valor. Crea un Wrapper del tipo correspondiente.

    Para una entrada int: crea un Wrapper[int], después usa Transform para duplicar el valor (devolviendo un int) y usa TransformToString para obtener una representación en forma de cadena.

    Para una entrada string: crea un Wrapper[string], después usa Transform para obtener la longitud de la cadena (devolviendo un int) y usa TransformToString para obtener la representación en forma de cadena.

    Imprime los resultados con 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, dada la siguiente entrada:

int
25

Tu salida debería ser:

Original: 25
Transformed: 50
As String: 25

Y dada la siguiente entrada:

string
Hello World

Tu salida debería ser:

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

La idea clave es que Transform recibe un Wrapper[T] como primer argumento e introduce un nuevo parámetro de tipo U para el tipo de retorno. Algo que no sería posible como método de la estructura. Este patrón te proporciona la flexibilidad de las transformaciones genéricas mientras trabajas dentro de las limitaciones del sistema de tipos de Go.

Pruébalo tú mismo

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Lee el indicador de tipo
	typeIndicator, _ := reader.ReadString('\n')
	typeIndicator = strings.TrimSpace(typeIndicator)
	
	// Lee el valor
	value, _ := reader.ReadString('\n')
	value = strings.TrimSpace(value)
	
	if typeIndicator == "int" {
		// Parsea el valor entero
		num, _ := strconv.Atoi(value)
		
		// TODO: Crea un Wrapper[int] con el número parseado
		
		// TODO: Usa Transform para duplicar el valor (pista: pasa una función que duplique)
		
		// TODO: Usa TransformToString para obtener la representación en string
		
		// TODO: Imprime 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: Crea un Wrapper[string] con el valor
		
		// TODO: Usa Transform para obtener la longitud del string (pista: pasa una función que devuelva len())
		
		// TODO: Usa TransformToString para obtener la representación en string
		
		// TODO: Imprime 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

Practica por tu cuenta: Compilador de Go online