Article complet
Cet article et deux autres ont été utilisés pour rédiger un article beaucoup plus complet. La lecture de l'article complet rend donc obsolète ce billet et les suivants :
L'article : Les procédures internes et les closures : une nouvelle manière de coder en Windev.
Avant-propos
Cet article concerne la programmation avec l'AGL Windev et présente une notion avancée dans l'utilisation du WLangage. Bien que le sujet soit extrêmement intéressant, il est utile d'avoir quelques notions en programmation pour le comprendre.
De plus, cet article traite des procédures internes qui sont apparues à partir de Windev 20. Les versions 19 et antérieures ne sont donc malheureusement pas concernées.
Les closures
Les closures ou clôtures / fermetures en français (je préfère l'anglais !) est un concept que je trouve difficile à expliquer, mais tellement simple à illustrer via un exemple.
Si vous le souhaitez vraiment, vous trouverez une définition sur wikipedia.
Je trouve qu'il est difficile de donner une définition claire à cette notion. C'est pourquoi je vais l'aborder petit à petit via des exemples.
Tout commença avec les procédures internes
Tout d'abord, une closure est une fonction retournée par une autre fonction. Cela est possible en Windev depuis l'apparition des procédures internes (si vous ne l'avez pas lu, je vous invite à commencer par l'article Windev - ou l'art d'utiliser les procédures internes).
PROCEDURE GenerateurDeClosure1()
PROCEDURE INTERNE iClosure()
Trace("Salut monde")
FIN
Closure est une Procédure = iClosure
RENVOYER Closure
Puis l'appel
MaClosure est une Procédure = generateurDeClosure1()
MaClosure()
Et la trace :
Salut monde
Notez que l'on est obligé de passer par des variables de type procédure pour convertir la procédure interne en procédure.
Jusque-là, rien de génial. Une fonction qui retourne une fonction... Si on s'arrête là, les closures ne servent à rien.
Puis la variable arriva
Une closure est une fonction qui a son propre environnement. Cet environnement est défini par les variables de la fonction qui génère la closure.
PROCEDURE GenerateurDeClosure2()
Compteur est un entier
PROCEDURE INTERNE iClosure()
Compteur ++
Trace(ChaîneConstruit("Salut monde %1", compteur))
FIN
Closure est une Procédure = iClosure
RENVOYER Closure
Puis l'appel
MaClosure1 est une Procédure = GenerateurDeClosure2()
MaClosure2 est une Procédure = GenerateurDeClosure2()
MaClosure1() // 1
MaClosure1() // 2
MaClosure2() // 1
MaClosure2() // 2
MaClosure1() // 3
MaClosure2() // 3
MaClosure2() // 4
MaClosure3 est une Procédure = GenerateurDeClosure2()
MaClosure1() // 4
MaClosure2() // 5
MaClosure3() // 1
Et la trace :
Salut monde 1
Salut monde 2
Salut monde 1
Salut monde 2
Salut monde 3
Salut monde 3
Salut monde 4
Salut monde 4
Salut monde 5
Salut monde 1
Cet exemple nous montre deux avantages des closures. Le premier avantage, c'est que cela nous permet de créer des fonctions qui mémorisent leur état. Inutile d'utiliser une variable globale ou une variable passée en paramètre par référence. Le second avantage, c'est qu'on est désormais capable de créer plusieurs fonctions qui ont le même comportement, mais qui ont des états différents.
Et le paramètre clôtura le tout
En utilisant des paramètres dans la procédure génératrice, on peut rendre la closure paramétrable.
PROCEDURE GenerateurDeClosure3(LOCAL pNom est chaîne)
Compteur est un entier
PROCEDURE INTERNE iClosure(pNomElement est chaîne)
Compteur ++
Trace(ChaîneConstruit("Salut %1 %2 tu es en position %3", pNom, pNomElement, Compteur))
FIN
Closure est une Procédure = iClosure
RENVOYER Closure
Puis l'appel
MaClosureAnimal est une Procédure = GenerateurDeClosure3("animal")
MaClosurePlanete est une Procédure = GenerateurDeClosure3("planete")
MaClosureVille est une Procédure = GenerateurDeClosure3("ville")
MaClosureAnimal("chien") // Salut animal chien tu es en position 1
MaClosurePlanete("Terre") // Salut planete Terre tu es en position 1
MaClosurePlanete("Vénus") // Salut planete Vénus tu es en position 2
MaClosureAnimal("chat") // Salut animal chat tu es en position 2
MaClosureVille("Paris") // Salut ville Paris tu es en position 1
MaClosureVille("New York") // Salut ville New York tu es en position 2
MaClosureVille("Amsterdam") // Salut ville Amsterdam tu es en position 3
Et la trace :
Salut animal chien tu es en position 1
Salut planete Terre tu es en position 1
Salut planete Vénus tu es en position 2
Salut animal chat tu es en position 2
Salut ville Paris tu es en position 1
Salut ville New York tu es en position 2
Salut ville Amsterdam tu es en position 3
Notez que les paramètres de la fonction génératrice doivent être passés en local, sinon, Windev n'appréciera pas.
À quoi ça sert ?
On peut s'en servir pour beaucoup de chose. À vous de trouver l'utilité.
Un petit exemple :
On souhaite utiliser une closure pour simplifier la fonction ExtraitChaine
. On a donc le code suivant :
MaChaine est une chaîne = "Ceci est une chaine à parcourir"
i est un entier = 1
MonMot est une chaîne = ExtraitChaîne(MaChaine, i, " ")
TANTQUE MonMot <> EOT
Trace(MonMot)
i ++
MonMot = ExtraitChaîne(MaChaine, i, " ")
FIN
qui devient
MaChaine est une chaîne = "Ceci est une chaine à parcourir"
Extrait est une Procédure = ExtraitChaineClosure(MaChaine," ")
MonMot est une chaîne = Extrait()
TANTQUE MonMot <> EOT
Trace(MonMot)
MonMot = Extrait()
FIN
grâce à la fonction
PROCEDURE ExtraitChaineClosure(LOCAL pChaineInitiale est chaîne, LOCAL pSéparateur est chaîne = TAB, LOCAL pSensParcours est un entier = DepuisDébut)
Compteur est un entier = 1
PROCEDURE INTERNE iExtrait(pPosition est entier = Compteur)
Résultat est une chaîne
Résultat = ExtraitChaîne(pChaineInitiale, pPosition, pSéparateur, pSensParcours)
SI Résultat <> EOT ALORS
Compteur = pPosition + 1
FIN
RENVOYER Résultat
FIN
Extrait est une Procédure = iExtrait
RENVOYER Extrait
Avec cette fonction, on n'a plus besoin de déclarer un compteur i. On n'a plus besoin d'indiquer à chaque fois le séparateur de chaine que l'on souhaite utiliser.
Encore plus loin
Vous pouvez utiliser une procédure interne pour créer votre générateur de closure. Il n'est pas obligatoire d'utiliser une procédure globale ou locale.
Conclusion
Avec cette nouveauté, Windev se dote d'un outil qui est présent dans d'autres langages (javascript, python, etc...). En comparant avec ces derniers, on constate qu'il y a encore des choses que Windev ne fait pas au niveau des closures, mais il sait faire le minimum, et c'est ce qu'on lui demande.
Le prochain article parlera d'un concept que j'appelle pseudo-classe et qui est très proche des closures.
Merci pour votre lecture.
Cet article vous a intéressé ? Vous voulez en savoir plus ? Suivez le lien !