Funciones Anónimas en Golang [#Go101]
Tags: backend, go
Published: 2024-02-21 23:05:46

### Introducción

Las funciones anónimas (funciones lambda o funciones literales) son aquellas que no están vinculadas a un identificador. En Golang son principalmente usadas para diferir tareas o ejecutarlas concurrentemente; para pasar funciones como argumentos, entre otros casos de uso que vamos a cubrir hoy. 

### Casos de uso

- Funciones como argumentos

 Cuando una función de orden superior tiene como parámetro otra función, es usual ver que funciones anónimas son pasadas como parámetros a estas. A esto también se le llama **“callbacks”**.

```go
func forEach(numbers []int, f func(int)) {
  for _, num := range numbers {
    f(num)
  }
}

func main() {
	forEach([]int{1, 2, 3}, func(i int) {
	  fmt.Printf("%d ", i * i) // Output: 1 4 9
	})
}
```
Ejecuta el código: {% embed https://go.dev/play/p/uZ0QxA0DObz %}

- Ejecuciones concurrentes (Goroutines)

Otro uso común es a la hora de lanzar Goroutines, sobre todo cuando la función es simple y sólo se utiliza en ese lugar. Esto nos permite mantener la lógica cerca de donde es utilizada, mejorando la legibilidad.

```go
func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		// Código que se ejecuta concurrentemente
		fmt.Println("Ejecutando en una goroutine")
		wg.Done()
	}()
	wg.Wait()

	fmt.Println("Ejecutando en la función main")
}
```
Ejecuta el código: {% embed https://go.dev/play/p/ELGG4mFCyqy %}

**ADVERTENCIA**: Funciones anónimas muy largas pueden generar el efecto contrario en la legibilidad, haciendo difícil entender el código.

- Diferir tareas

Cuando queremos diferir la ejecución de más de una función en general utilizamos funciones anónimas.

```go
func main() {
	now := time.Now()
	defer func() {
		duration := time.Since(now)
		fmt.Printf("Se tardó %v en ejecutar esto", duration)
	}()

	for i := range 1_001 {
		if i%100 == 0 {
			fmt.Printf("%d ", i)
			time.Sleep(time.Millisecond * 100)
		}
	}
	fmt.Printf("\n")
} 
// Output:
// 0 100 200 300 400 500 600 700 800 900 1000
// Se tardó 1.1s en correr esto
```
Ejecuta el código: {% embed https://go.dev/play/p/8iAbd9aI8Dv %}

- Clausuras o closures

Las funciones anónimas pueden capturar variables dentro de su scope y mantener acceso a ellas aún cuando la función exterior o padre ya ha retornado. Esto nos permite crear funciones que “recuerdan” estado.

```go
func makeMultiplier(factor int) func(int) int {
	return func(x int) int {
		return x * factor
	}
}

func main() {
	multiplyByTwo := makeMultiplier(2)
	r1 := multiplyByTwo(2)
	fmt.Println(r1) // Output: 4
	r2 := multiplyByTwo(5)
	fmt.Println(r2) // Output: 10
}
```
Ejecuta el código: {% embed https://go.dev/play/p/6tnfPMcsvYp %}

**ADVERTENCIA**: A pesar de su potencial, hay que saber cuándo y cómo utilizar closures porque pueden traer problemas de uso de memoria, de legibilidad; bugs a la hora de manejar ese “estado”, sobre todo concurrentemente; o dificultades a la hora de debuggear.

- Testing

Solemos usar funciones anónimas para correr “sub tests” en Table-Driven Tests:

```go
func Add(a, b int) int {
	return a + b
}

func TestAdd(t *testing.T) {
	testCases := []struct {
		a, b, expected int
	}{
		{1, 2, 3},
		{0, 0, 0},
		{-1, 1, 0},
	}

	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%d+%d", tc.a, tc.b), func(t *testing.T) {
			result := Add(tc.a, tc.b)
			if result != tc.expected {
				t.Errorf("Expected %d, got %d", tc.expected, result)
			}
		})
	}
}
```

Volviendo a las clausuras o closures, el siguiente es un patrón que le robé a [Mat Ryer] (https://twitter.com/matryer) en una de sus [charlas en GopherCon UK](https://youtu.be/yeetIgNeIkc?si=DZiUTlqOSE-k-Om9&t=600) que recomiendo enormemente.

```go
func TestSomething(t *testing.T) {
	file, teardown, err := setup(t)
	defer teardown()
	if err != nil {
		t.Error("setup:", err)
	}
	// hacer algo con el archivo
}

func setup(t *testing.T) (*os.File, func(), error) {
	teardown := func() {}
	// crear archivo de test
	file, err := os.CreateTemp(os.TempDir(), "test")
	if err != nil {
		return nil, teardown, err
	}

	teardown = func() {
		// cerrar archivo
		err := file.Close()
		if err != nil {
			t.Error("setup: Close:", err)
		}
		// borrar archivo de prueba
		err = os.RemoveAll(file.Name())
		if err != nil {
			t.Error("setup: RemoveAll:", err)
		}
	}
	return file, teardown, nil
}

```

### Pros y contras

- Pros:

1. Concisión: Permiten definir y usar funciones directamente donde las necesitas, lo que reduce el desorden en tu código y el tamaño de tus APIs internas o privadas.
2. Legibilidad (para tareas simples): las funciones anónimas pueden hacer el código más fácil de leer manteniéndolo cerca de donde se utiliza.

- Contras:

1. Testabilidad y debugging: al no estar explicitamente definidas y nombradas, pueden ser más difíciles de testear y debuggear.
2. Legibilidad (para tareas largas o complejas): cuando esto sucede, el código puede ser más difícil de comprender.

### Conclusiones

Las funciones anónimas pueden ser una herramienta muy poderosa, pero hay que elegir cuidadosamente cuando las utilizamos. En resumen, puedes utilizarlas cuando su lógica es simple y corta, y no va a ser reutilizada; y/o cuando necesites hacer uso de closures.

En los casos en los que su lógica se vuelve muy compleja o la función va a ser reutilizada en otros lugares, considera definirla como una función nombrada para mayor mantenibilidad y claridad.