Funzioni Anonime e Closure in Go
Le funzioni anonime (o lambda) in Go sono funzioni senza nome che possono essere definite inline, assegnate a variabili e passate come argomenti. Le closure sono funzioni anonime che catturano e utilizzano variabili dallo scope circostante. Queste funzionalita rendono Go un linguaggio con supporto alla programmazione funzionale. Esploriamo in dettaglio questi concetti.
Definizione di Funzione Anonima
Una funzione anonima viene dichiarata come una funzione normale ma senza un nome. Puo essere assegnata a una variabile per essere richiamata in seguito.
package main
import "fmt"
func main() {
saluta := func(nome string) {
fmt.Println("Ciao,", nome)
}
saluta("Anna") // Ciao, Anna
saluta("Marco") // Ciao, Marco
}
La variabile saluta contiene un riferimento alla funzione anonima e puo essere chiamata come una funzione ordinaria.
Funzione Anonima Invocata Immediatamente (IIFE)
Una funzione anonima puo essere definita e invocata immediatamente aggiungendo le parentesi di chiamata subito dopo la definizione.
package main
import "fmt"
func main() {
risultato := func(a, b int) int {
return a + b
}(5, 3)
fmt.Println("Risultato:", risultato) // Risultato: 8
}
Questo pattern e utile quando si ha bisogno di eseguire un blocco di codice isolato con il proprio scope.
func main() {
func() {
fmt.Println("Eseguita immediatamente!")
}()
}
Closure: Cattura delle Variabili
Una closure e una funzione anonima che cattura variabili dallo scope in cui e definita. La funzione mantiene un riferimento a queste variabili, non una copia.
package main
import "fmt"
func main() {
contatore := 0
incrementa := func() int {
contatore++
return contatore
}
fmt.Println(incrementa()) // 1
fmt.Println(incrementa()) // 2
fmt.Println(incrementa()) // 3
fmt.Println("Contatore:", contatore) // 3
}
La funzione incrementa cattura la variabile contatore per riferimento. Ogni chiamata modifica la stessa variabile.
Closure come Generatori
Le closure possono essere usate per creare funzioni generatrici con stato interno.
package main
import "fmt"
func generatoreFibonacci() func() int {
a, b := 0, 1
return func() int {
valore := a
a, b = b, a+b
return valore
}
}
func main() {
fib := generatoreFibonacci()
for i := 0; i < 10; i++ {
fmt.Print(fib(), " ")
}
fmt.Println()
// Output: 0 1 1 2 3 5 8 13 21 34
}
Ogni chiamata a generatoreFibonacci() crea una nuova closure con il proprio stato indipendente.
Funzioni come Argomenti
Le funzioni possono essere passate come argomenti ad altre funzioni, permettendo pattern come callback e strategie.
package main
import "fmt"
func applicaASlice(numeri []int, trasformazione func(int) int) []int {
risultato := make([]int, len(numeri))
for i, n := range numeri {
risultato[i] = trasformazione(n)
}
return risultato
}
func main() {
numeri := []int{1, 2, 3, 4, 5}
doppi := applicaASlice(numeri, func(n int) int {
return n * 2
})
fmt.Println("Doppi:", doppi) // [2 4 6 8 10]
quadrati := applicaASlice(numeri, func(n int) int {
return n * n
})
fmt.Println("Quadrati:", quadrati) // [1 4 9 16 25]
}
Funzioni che Restituiscono Funzioni
Una funzione puo restituire un’altra funzione, creando una factory di comportamenti.
package main
import "fmt"
func moltiplicatorePer(fattore int) func(int) int {
return func(n int) int {
return n * fattore
}
}
func main() {
triplo := moltiplicatorePer(3)
quintuplo := moltiplicatorePer(5)
fmt.Println(triplo(10)) // 30
fmt.Println(quintuplo(10)) // 50
}
La funzione restituita cattura il parametro fattore come closure.
Attenzione alla Cattura per Riferimento nei Cicli
Un errore comune con le closure nei cicli: tutte le closure catturano la stessa variabile.
package main
import "fmt"
func main() {
funzioni := make([]func(), 5)
// ERRORE COMUNE: tutte le closure catturano lo stesso 'i'
for i := 0; i < 5; i++ {
funzioni[i] = func() {
fmt.Print(i, " ")
}
}
for _, f := range funzioni {
f() // Stampa: 5 5 5 5 5 (non 0 1 2 3 4!)
}
fmt.Println()
// SOLUZIONE: creare una copia locale della variabile
for i := 0; i < 5; i++ {
i := i // Nuova variabile con lo stesso nome nello scope del ciclo
funzioni[i] = func() {
fmt.Print(i, " ")
}
}
for _, f := range funzioni {
f() // Stampa: 0 1 2 3 4
}
fmt.Println()
}
Pattern Middleware con Funzioni Anonime
Un uso pratico delle funzioni come argomenti e il pattern middleware.
package main
import (
"fmt"
"time"
)
type Operazione func(string) string
func conLog(op Operazione) Operazione {
return func(input string) string {
inizio := time.Now()
risultato := op(input)
fmt.Printf("Operazione eseguita in %v\n", time.Since(inizio))
return risultato
}
}
func main() {
maiuscolo := func(s string) string {
return fmt.Sprintf("[%s]", s)
}
maiuscoloConLog := conLog(maiuscolo)
risultato := maiuscoloConLog("test")
fmt.Println(risultato)
}
Conclusione
Le funzioni anonime e le closure in Go offrono un potente strumento per la programmazione funzionale. Le closure catturano le variabili per riferimento, il che le rende ideali per generatori, callback e pattern middleware. E importante prestare attenzione alla cattura delle variabili nei cicli per evitare comportamenti inattesi. La capacita di passare e restituire funzioni rende il codice Go flessibile e componibile.