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 !

Jonathan Laurent

Read more posts about this author.

Comments