Funciones como valores y tipos de función en Go

En Go, las funciones son ciudadanos de primera clase. Esto significa que puedes tratarlas como cualquier otro tipo de dato: puedes asignarlas a variables, pasarlas como argumentos a otras funciones o incluso retornarlas como valores desde una función. Esta característica no es un simple detalle sintáctico; es lo que permite implementar patrones de diseño potentes como el de Strategy o Middleware sin necesidad de recurrir a jerarquías complejas de interfaces.

Este modelo funciona porque, internamente, una variable de función es un puntero a una estructura que contiene el código de la función y su contexto (clausura). Utilizarás este patrón cuando necesites desacoplar la lógica de ejecución de la lógica de decisión, como en un enrutador de HTTP o un motor de reglas. También es útil para implementar tablas de despacho (dispatch tables), que son mapas de funciones que evitan bloques switch o if-else interminables. Sin embargo, debes tener cuidado: si intentas llamar a una variable de función que no ha sido inicializada, el programa sufrirá un panic por puntero nulo en tiempo de ejecución.

Es importante notar que, para que dos funciones sean consideradas del mismo tipo de función, deben tener exactamente la misma firma: los mismos tipos de parámetros y la misma lista de retornos.

package main

import "fmt"

// Task define un tipo de función nombrado. Esto permite que la firma
// sea un tipo concreto y reutilizable en todo el proyecto.
type Task func(int) int

// CreateMultiplier es una función de orden superior que retorna otra función.
// La función interna "captura" el valor de factor, creando una clausura.
func CreateMultiplier(factor int) Task {
	return func(n int) int {
		return n * factor
	}
}

func main() {
	// Asignación de una función anónima a una variable de tipo Task.
	var addOne Task = func(n int) int {
		return n + 1
	}

	// Creación de funciones mediante fábricas (factories).
	double := CreateMultiplier(2)
	triple := CreateMultiplier(3)

	// Implementación de una tabla de despacho (dispatch table) con un map.
	// Esto permite una búsqueda O(1) para encontrar la lógica a ejecutar.
	operations := map[string]Task{
		"incrementar": addOne,
		"doblar":      double,
		"triplicar":   triple,
	}

	input := 10

	// Iteramos sobre el mapa para ejecutar las funciones guardadas.
	for name, execute := range operations {
		// Llamamos a la función almacenada en la variable 'execute'.
		result := execute(input)
		fmt.Printf("Operación %s: %d -> %d\n", name, input, result)
	}
}

En el código anterior, empezamos definiendo Task como un tipo de función nombrado. Esto es fundamental para que la firma func(int) int sea un tipo con nombre propio y no una expresión anónima, lo que facilita la lectura y la validación en tiempo de compilación.

La función CreateMultiplier es un ejemplo de una función que retorna una función. Al devolver func(n int) int, estamos creando una clausura (closure); la función interna mantiene acceso al parámetro factor incluso después de que CreateMultiplier haya terminado su ejecución.

En el main, la variable operations utiliza el map para actuar como una tabla de despacho. En lugar de usar un switch para decidir qué operación hacer, mapeamos una clave de tipo string directamente a un valor de tipo Task. Esto hace que el código sea extremadamente fácil de extender: para añadir una nueva operación, solo tienes que insertar un nuevo par clave-valor en el mapa, sin modificar la lógica de iteración.

El acceso a execute(input) dentro del bucle range es la ejecución de una función almacenada en una variable. En este punto, el runtime de Go sigue el puntero de la función asociada a la clave del mapa para saltar al código correspondiente.

El error frecuente

Si declaras una variable de un tipo de función pero olvidas inicializarla, intentarás llamar a nil.

var f Task // f es nil
f(10)       // panic: runtime error: invalid memory address or nil pointer dereference

Para evitar esto, siempre verifica si la función es distinta de nil antes de invocarla si la variable puede ser opcional, o asegúrate de que la lógica de inicialización sea robusta.

43

Dejar un comentario

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

Scroll al inicio