Hostinger

Closure ! 😓

Les closures sont un concept puissant en programmation qui permet de capturer et de se souvenir des variables de leur environnement. Cela permet de créer des fonctions plus dynamiques et flexibles.

Qu'est-ce qu'un closure ?

Une closure est une fonction qui "se souvient" de l’environnement dans lequel elle a été créée.

Autrement dit, une closure peut capturer et conserver des variables externes même après que la fonction extérieure ait terminé son exécution.

Exemple simple

Imagine que tu ouvres une boîte et que tu mets un chiffre à l’intérieur. Ensuite, chaque fois que tu ouvres cette boîte, tu peux voir et modifier ce chiffre.

Une closure, c’est un peu comme une boîte qui se souvient de son contenu ! 📦

Créer une closure

Création d'un closure pas à pas

Étape 1 : Créer la fonction

func counter() {
}

Étape 2 : Ajouter le type de retour

On va retourner une fonction anonyme, que l'on appelle closure dans ce contexte.

func counter() func() int {
}

Étape 3 : Ajouter une variable capturée

func counter() func() int {
    count := 0 // Cette variable sera capturée par la closure
}

La valeur de la variable count sera retenue par la closure.

Étape 4 : Retourner la fonction anonyme (closure)

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

Closure terminée

Et voilà, c’est fini ! La closure est prête ! Ne faites pas attention au 5, c’est juste un chiffre choisi au hasard pour l’exemple.

Avec un paramètre

Voici un exemple où nous définissons une fonction multiplier qui retourne une closure :

package main

import "fmt"

// Fonction qui retourne une closure
func multiplier(factor int) func(int) int {
    return func(number int) int {
        return number * factor
    }
}

func main() {
    // Création d'une closure qui multiplie par 2
    double := multiplier(2)
    // Création d'une closure qui multiplie par 3
    triple := multiplier(3)

    fmt.Println("Double de 5 :", double(5))   // Affiche : Double de 5 : 10
    fmt.Println("Triple de 5 :", triple(5))   // Affiche : Triple de 5 : 15
}

Dans cet exemple, La fonction multiplier prend un nombre factor et retourne une nouvelle fonction (une closure).

Cette closure prend un autre nombre number et le multiplie par factor.

Quand on crée double := multiplier(2), on fabrique une closure qui se souvient que factor vaut 2.

Quand on appelle double(5), la closure se souvient de factor et retourne 5 * 2 = 10.

Les closures permettent donc de créer des fonctions "prêtes à l’emploi".

Une closure dans le langage Go

Avec une variable externe 💪

Lorsqu’une closure est créée, elle capture les variables de son environnement externe. Ces variables restent accessibles même si la fonction extérieure a terminé son exécution.

Prenons un autre exemple pour bien comprendre :

package main

import "fmt"

// Fonction qui retourne une closure qui incrémente un compteur
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    c := counter()

    fmt.Println(c()) // Affiche : 1
    fmt.Println(c()) // Affiche : 2
    fmt.Println(c()) // Affiche : 3
}

Dans le code ci-dessus, la fonction counter initialise une variable count à 0 et retourne une closure qui incrémente et renvoie cette variable chaque fois qu’elle est appelée.

Chaque appel à c() incrémente count de 1, ce qui prouve que la closure retient la valeur de count entre les appels, même après que la fonction counter ait terminé.

Closure variable environnement

🦸🏻‍♂️ Encore mieux comprendre en comparant une fonction normal et une closure !

"Transformer" la closure en fonction normal

Pour encore mieux comprendre les closures, nous allons adapter la dernière closure pour la transformer en fonction :

package main

import "fmt"

// -- Closure --
func counterClosure() func() int {

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

}

// -- Fonction --
func counterFunction() int {

	count := 0
	count++
	return count

}

func main() {

	fmt.Println(" -- Closure --")

	c := counterClosure()
	fmt.Println(c()) // Affiche : 1
	fmt.Println(c()) // Affiche : 2
	fmt.Println(c()) // Affiche : 3

	fmt.Println(" -- Fonction --")

	fmt.Println(counterFunction()) // Affiche : 1
	fmt.Println(counterFunction()) // Affiche : 1
	fmt.Println(counterFunction()) // Affiche : 1

}

Résultat !

on exécute le programme :

go run main/go

Résultat :

 -- Closure --
1
2
3
 -- Fonction --
1
1
1

Comparer

Vous avez peut-être remarqué une différence avec la variable count.

Dans une closure, la valeur de count est mémorisée entre chaque appel de la fonction. En revanche, dans une fonction normale, count est réinitialisé à chaque appel.

ClosureFonction
Variable persistante ?✅ Oui❌ Non
Réinitialisation ?❌ Non✅ Oui à chaque appel

Conclusion

  • Une closure mémorise la variable et la garde entre les appels.

  • Une fonction classique réinitialise la variable à chaque appel.