Modifier

Je vous ai parlé il y a quelques jours des fonctions lambdas dans WinDev. Avec cette nouveauté, WinDev® a décidé de s'essayer à la programmation fonctionnelle.

Je ne suis pas un spécialiste de ce paradygme. A l'heure actuelle, je préfère la POO. C'est pourquoi je n'entrerai pas dans les détails pour parler de quelque chose que je ne maîtrise pas.

Nous nous contenterons de voir le fonctionnement des 3 nouvelles fonctions : Transforme (map), Filtre (filter) et Agrège (reduce).

Transforme / Map ou la transformation d'un tableau en un autre tableau

La fonction Transforme permet de transformer un tableau en un autre tableau. Je pense qu'un exemple sera plus simple à comprendre.

Exemple

J'ai un tableau contenant des clients. Je souhaite extraire tous les codes postaux de ces clients pour faire une statistique dessus. Le résultat devra être un tableau.

Voici le code qui définit un client et qui initialise notre tableau :

stClient est une structure
    id est un entier
    nom est une chaine
    ...
    code_postal est une chaine
    ville est une chaine
    chiffre_affaire est monétaire
fin

clients est un tableau de stClient = lister_tous_les_clients()

Avant Transforme, on aurait eu le code suivant pour extraire les codes postaux :

codes_postaux est un tableau de chaine

POUR TOUT ELEMENT client DE clients
    codes_postaux.ajoute(client.code_postal)
FIN

Avec Transforme (et une fonction lambda), le code se résumerait à ce qui suit :

codes_postaux est un tableau de chaine = clients.transforme(c => c.code_postal)

Voire si on veut pousser le vice jusqu'au bout :

soit codes_postaux = clients.transforme(c => c.code_postal)

Le gain de code n'est pas négligeable, et pour peu que l'on arrive à lire cette nouvelle syntaxe, on ne perd pas en lisibilité.

Je n'ai pas pu tester cette nouvelle version (j'espère mettre la main dessus dès qu'elle sera disponible), mais en théorie, l'utilisation de Transforme devrait aussi être plus performant que l'utilisation d'une boucle standard.

Filtre / Filter ou le filtrage des éléments d'un tableau vers un autre tableau

La fonction Filtre permet de filtrer les éléments d'un tableau. Le résultat est un nouveau tableau. Continuons avec notre exemple.

Exemple

Nous avons toujours le tableau contenant des clients. Cette fois, nous souhaitons obtenir tous les clients qui habitent dans la Drôme (département 26).

Avant Filtre nous aurions eu le code suivant :

clients_du_26 est un tableau de stClient

POUR TOUT ELEMENT client de clients
    SI client.code_postal [= "26" ALORS
        clients_du_26.ajoute(client)
    FIN
FIN

Avec Filtre, le code devient plus court :

clients_du_26 est un tableau de stClient = clients.filtre(c => c.code_postal [= "26")

Le gain de code est vraiment intéressant dans ce cas.

Agrège / Reduce ou la transformation de vos résultats

Agrège permet de transformer une suite d'élément en un seul élément. C'est la fonction la plus complexe des trois, d'une part, parce que le résultat obtenu n'est pas forcément un tableau, d'autre part parce que les paramètres de la fonction lambda ne sont pas intuitifs).

Les paramètres

Voici le code de l'exemple fourni par PCSoft. Notez bien que tout ce que je vais expliquer est une pure supposition.

MesClients est un tableau d'enregistrements de Client
...
SommesEncours est un monétaire
SommesEncours = MesClients.Agrège( (x, total) => { total += x.EnCoursAutorisé }, 0)

Et ça s'arrête là. Je vais réecrire la dernière ligne avec des lettres pour voir chaque partie.

SommesEncours = MesClients.Agrège( (A) => { B }, C)

La partie A, comme nous l'avons vu, sont les paramètres de la foction lambda. Au vu de l'exemple, je comprends que le paramètre x correspond à l'élément courant (un client) et que le paramètre total correspond au cumul des encours autorisés des clients précédents. Si l'on remplace la lambda par une fonction traditionnelle, nous aurions le code suivant :

PROCEDURE sommer_encours(client_courant est un enregistrement de Client, total_encours est un monétaire)
    total_encours += client_courant.EnCoursAutorisé
    RENVOYER total_encours
FIN

En résumé, pour chaque élément, on récupère le résultat qui a été calculé précédement et on modifie ce résultat avec les données de l'élément courant. Je ne vous en voudrai pas si vous trouvez cela un peu compliqué.

La partie B est le code de la lambda. Il semble très différent du code de la procédure traditionnelle. Nous verrons cela un peu plus bas dans l'article.

Et la partie C, ce petit 0, que signifie-t-il ? Je suppose tout simplement que c'est la valeur du total au moment de la première itération. En effet, que vaudrait total au premier passage si on ne le détermine pas ? Et aussi, quel serait son type ?

En effet, la plupart des calculs se feront sur des numériques, mais si on souhaite passer une chaîne de caractère ? Et bien, ce paramètre en position C devrait aussi permettre de typer la valeur de retour.

Exemple

Nous avons vu un exemple de la fonction Agrège ci-dessus, mais pour le plaisir, je vous mets un exemple avec mes données. Ici, nous souhaitons obtenir le chiffre d'affaire de tous les clients

Avant la fonction Agrège, nous avions le code suivant :

chiffre_affaire_total est un monétaire

POUR TOUT ELEMENT client de clients
    chiffre_affaire_total += client.chiffre_affaire
FIN

Avec la fonction Agrège, on l'écrit ainsi :

chiffre_affaire_total est un monétaire = clients.Agrège((c, ca_total) => {ca_total += c.chiffre_affaire}, 0)

Là aussi, on gagne en ligne de code, mais je trouve que l'on perd beaucoup en lisibilité.

Tant pis, la fonction Agrège n'est peut-être pas si intéressante que ça...

C'est plus court, mais c'est quoi l'intéret de tout cela ?

Je plaisante ! La fonction Agrège est très intéressante, ainsi que les fonction Transforme et Filtre. Pourquoi ? Parce qu'on peut les combiner (enfin, j'espère qu'on pourra le faire parce que l'exemple ne donne pas plus de détail).

Je souhaite le chiffre d'affaire de tous les clients du 26 ?

Avant :

chiffre_affaire_du_26 est un monétaire

POUR TOUT ELEMENT client de clients
    SI client.code_postal [= "26" ALORS
        chiffre_affaire_du_26 += client.chiffre_affaire
    FIN
FIN

Après :

chiffre_affaire_du_26 est un monétaire
chiffre_affaire_du_26 = clients.filtre(c => c.code_postal [= "26").Agrège((c, ca_total) => { ca_total += c.chiffre_affaire }, 0)

Je souhaite obtenir les codes postaux de tous les clients du 26 ?

codes_postaux_du_26 est un tableau de chaine
codes_postaux_du_26 = clients.filtre(c => c.code_postal [= "26").Transforme( c =>  c.code_postal)

On obtient un code plus court, et une fois habitué à cette syntaxe originale, j'estime qu'on ne perd pas trop en lisibilité. En théorie, on devrait aussi gagner en performance, mais ça, seul l'avenir nous le dira...

Lambda, mais où est le RENVOYER ?

En examinant le code des lambdas et l'alternative traditionnelle, vous avez du vous demander où était passé le RENVOYER, n'est-ce pas ? Le RENVOYER a disparu... pour laisser place à la magie de la programmation fonctionnelle.

Du peu que je sais, dans le paradygme fonctionnel, la dernière expression évaluée devient le résultat de la fonction. Lorsqu'on fait ca_total += c.chiffre_affaire, ca_total est la dernière expression évaluée et est donc le résultat de la fonction.

Autre exemple, c => c.code_postal [= "26", la dernière expression est c.code_postal [= "26". On renvoie donc vrai ou faux.

Conclusion

Il est trop tôt pour savoir si ces fonctions deviendront un incontournable de nos futurs programmes. Par contre, je tiens à féliciter PCSoft qui essaye d'intégrer de nouvelles notions dans son langage afin de le rendre encore plus riche.

Dans tous les cas, je vous invite à les essayer et voir si cela peut vous apporter quelque chose. Personnellement, je vais essayer de m'amuser avec pour voir ce qu'elles ont dans le ventre ces fonctions. Ce sera l'occasion d'un nouvel article... l'année prochaine.

J'espère que vous avez pris plaisir à lire cet article autant que j'en ai pris à l'écrire.

Je m'excuse d'avance pour tous les professionnels de la programmation fonctionnelle, car j'ai certainement raconté beaucoup de bêtises. N'hésitez pas à me corriger dans les commentaires si vous le souhaitez.

Pour les autres, n'hésitez pas à partager vos idées à propos de l'utilisation de ces outils.

Merci à vous tous !

Article suivant Article précédent

Blog Comments powered by Disqus.