In questo articolo, esploreremo tre concetti avanzati legati alle funzioni in Go: i riferimenti a funzione, le funzioni anonime e le closure. Questi strumenti permettono di scrivere codice più modulare, flessibile e riutilizzabile, e sono essenziali per padroneggiare Go.

1. Riferimenti a Funzione

In Go, le funzioni sono cittadini di prima classe, il che significa che possono essere assegnate a variabili, passate come argomenti ad altre funzioni e restituite come valori da altre funzioni. Questo apre la porta a una serie di possibilità avanzate, tra cui l’uso di riferimenti a funzione.

1.1. Assegnazione di Funzioni a Variabili

In Go, è possibile assegnare una funzione a una variabile, creando un riferimento a quella funzione. Ecco un esempio:

package main

import "fmt"

func saluta(nome string) {
    fmt.Println("Ciao,", nome)
}

func main() {
    f := saluta // Assegna la funzione "saluta" alla variabile "f"
    f("Alice")  // Chiama la funzione attraverso la variabile
}

Output:

Ciao, Alice

In questo esempio, f è un riferimento alla funzione saluta, e può essere chiamata come una normale funzione.

1.2. Passaggio di Funzioni come Argomenti

Le funzioni possono essere passate come argomenti ad altre funzioni. Questo è particolarmente utile per implementare callback o strategie. Ecco un esempio:

package main

import "fmt"

func esegui(f func(string), nome string) {
    f(nome)
}

func saluta(nome string) {
    fmt.Println("Ciao,", nome)
}

func main() {
    esegui(saluta, "Bob") // Passa la funzione "saluta" come argomento
}

Output:

Ciao, Bob

1.3. Restituzione di Funzioni da Altre Funzioni

Le funzioni possono anche essere restituite come valori da altre funzioni. Questo è utile per creare funzioni factory o per implementare comportamenti dinamici. Ecco un esempio:

package main

import "fmt"

func creaSalutatore(prefisso string) func(string) {
    return func(nome string) {
        fmt.Println(prefisso, nome)
    }
}

func main() {
    salutatore := creaSalutatore("Benvenuto,")
    salutatore("Charlie") // Output: Benvenuto, Charlie
}

2. Funzioni Anonime

Le funzioni anonime sono funzioni senza nome che possono essere definite inline. Sono spesso utilizzate per implementare callback o per eseguire operazioni temporanee.

2.1. Definizione di Funzioni Anonime

Ecco un esempio di funzione anonima:

package main

import "fmt"

func main() {
    func() {
        fmt.Println("Questa è una funzione anonima!")
    }() // La funzione viene chiamata immediatamente
}

Output:

Questa è una funzione anonima!

2.2. Utilizzo di Funzioni Anonime come Callback

Le funzioni anonime sono spesso utilizzate come callback. Ecco un esempio:

package main

import "fmt"

func eseguiConCallback(f func()) {
    fmt.Println("Prima dell'esecuzione")
    f()
    fmt.Println("Dopo l'esecuzione")
}

func main() {
    eseguiConCallback(func() {
        fmt.Println("Callback eseguito")
    })
}

Output:

Prima dell'esecuzione
Callback eseguito
Dopo l'esecuzione

3. Closure

Le closure sono funzioni che catturano e conservano le variabili dell’ambiente circostante. In Go, le closure sono create utilizzando funzioni anonime che accedono a variabili definite al di fuori del loro corpo.

3.1. Creazione di Closure

Ecco un esempio di closure che mantiene uno stato interno:

package main

import "fmt"

func contatore() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    incrementa := contatore()
    fmt.Println(incrementa()) // Output: 1
    fmt.Println(incrementa()) // Output: 2
    fmt.Println(incrementa()) // Output: 3
}

In questo esempio, la funzione contatore restituisce una closure che incrementa e restituisce il valore di count ogni volta che viene chiamata.

3.2. Utilizzo di Closure per Creare Funzioni Factory

Le closure sono spesso utilizzate per creare funzioni factory, ovvero funzioni che generano altre funzioni con comportamenti specifici. Ecco un esempio:

package main

import "fmt"

func creaMoltiplicatore(fattore int) func(int) int {
    return func(numero int) int {
        return numero * fattore
    }
}

func main() {
    doppio := creaMoltiplicatore(2)
    triplo := creaMoltiplicatore(3)

    fmt.Println(doppio(5))  // Output: 10
    fmt.Println(triplo(5))  // Output: 15
}

4. Esempi Pratici

Vediamo alcuni esempi pratici di come utilizzare riferimenti a funzione, funzioni anonime e closure in Go.

4.1. Filtrare una Slice con una Funzione di Callback

Ecco un esempio di come utilizzare una funzione di callback per filtrare una slice:

package main

import "fmt"

func filtra(slice []int, condizione func(int) bool) []int {
    var risultato []int
    for _, valore := range slice {
        if condizione(valore) {
            risultato = append(risultato, valore)
        }
    }
    return risultato
}

func main() {
    numeri := []int{1, 2, 3, 4, 5, 6}
    pari := filtra(numeri, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println(pari) // Output: [2 4 6]
}

4.2. Creare una Funzione di Logging Dinamica

Ecco un esempio di come utilizzare una closure per creare una funzione di logging dinamica:

package main

import "fmt"

func creaLogger(prefisso string) func(string) {
    return func(messaggio string) {
        fmt.Printf("[%s] %s\n", prefisso, messaggio)
    }
}

func main() {
    logger := creaLogger("INFO")
    logger("Avvio dell'applicazione") // Output: [INFO] Avvio dell'applicazione
    logger("Operazione completata")   // Output: [INFO] Operazione completata
}

5. Conclusioni

I riferimenti a funzione, le funzioni anonime e le closure sono strumenti potenti in Go che permettono di scrivere codice più modulare, flessibile e riutilizzabile. Con questa guida, hai imparato come utilizzare questi concetti per creare funzioni dinamiche, gestire callback e mantenere stati interni. Queste tecniche sono essenziali per padroneggiare Go e per scrivere applicazioni complesse ed efficienti. Ora sei pronto per sfruttare al meglio queste funzionalità nei tuoi progetti!