Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Débats sur le développement - Le Best Of Discussion :

Pourquoi la programmation fonctionnelle n’est-elle pas la norme de l’industrie du code ?


Sujet :

Débats sur le développement - Le Best Of

  1. #141
    Expert éminent sénior
    Citation Envoyé par Pyramidev Voir le message
    Oui, c'est à peu près ce que Richard Feldman entend par le style fonctionnel, mis à part qu'il évoque aussi l'immuabilité.
    D'ailleurs, si on évoque l'immuabilité, je présumes que le fonctionnel peut être très très coûteux lorsqu'on travaille sur des ensembles assez gros pour qui la muabilité permet d'éviter des recopies.

    Citation Envoyé par Pyramidev Voir le message
    Or, dans la vidéo, de 35m50 à 36m30, Richard Feldman explique ce qu'il entend par le style fonctionnel. Et il définit le style fonctionnel par Avoid mutation and side effects en précisant bien qu'on peut le faire dans les langages qui autorisent la muabilité et les effets de bord, même s'il est vrai que certains langages supportent mieux le fonctionnel que d'autres.
    Dans la page Wikipédia, c'est :
    En pratique, pour des raisons d'efficacité, et du fait que certains algorithmes s'expriment aisément avec une machine à états, certains langages fonctionnels autorisent la programmation impérative en permettant de spécifier que certaines variables sont assignables (ou mutables selon la dénomination habituelle), et donc la possibilité d'introduire localement des effets de bord[réf. nécessaire]. Ces langages sont regroupés sous le nom de langages fonctionnels impurs. Les langages dits purement fonctionnels n'autorisent pas la programmation impérative1. De fait, ils sont dénués d'effets de bord et protégés contre les problèmes que pose l'exécution concurrente. On peut voir par exemple ce qui a été fait dans le cadre du langage Erlang.
    https://fr.wikipedia.org/wiki/Progra...ef-monades_1-0

    In functional code, the output value of a function depends only on its arguments, so calling a function with the same value for an argument always produces the same result. This is in contrast to imperative programming where, in addition to a function's arguments, global program state can affect a function's resulting value.
    Programming in a functional style can be accomplished in languages that are not specifically designed for functional programming
    i.e. on ne fait pas du fonctionnel, mais on fait "comme" du fonctionnel.

    https://en.wikipedia.org/wiki/Functi...al_programming


    Par exemple pour la mutabilité, si toutes tes variables ne sont pas "const" par défaut, ou si ton véficateur syntaxique ne t'insulte pas en cas de variables non-const non explicitées, alors de fait, tu programmeras potentiellement en impératif, mais pas en fonctionnel. Un exemple tout bête, si tu ne vérifies pas que tu as bien mis "constexpr" devant chaque fonction en C++, ton compilateur va pas pouvoir faire toutes les optis, tu ne sera pas en fonctionnel. Et sur des millions de lignes de codes, d'une 50ène de développeurs/stagiaire, t'en auras toujours un qui aura fait la bourde, si tu ne vérifie pas e.g. grâce au langage, ou via des hooks gits.
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  2. #142
    Expert éminent sénior
    C'est comme les allergies dans un restaurant, si la loi/norme/certification t'oblige à spécifier les risques allergènes, soit tes plats ne contiennent pas de produits allergènes et tu ne marques rien, soit tu les explicites dans le menu. Et tu t'assures que chacun des plats sans "marques" ne contient pas de produits allergènes.

    Si tu ne le fait pas alors tu ne suis pas la loi/norme/certification. Ben pour le fonctionnel, c'est pareil.



    Et rien ne t'empêche de spécifier dans ton menu des plats "vérifiés" soit n'ayant pas des produits alergènes, soit les indiquant, et idem, tu t'assures que chacun de ces plats-ci, sans indications de produits alergènes, n'en contiennent pas. Dans ce cas tu auras une partie de ton menu non-fonctionnel, et une autre partie fonctionnelle que tu auras explicitement indiqué "vérifié". Mais dans ce cas là, tous les produits non-explicitement marqué "vérifiés" seront non-fonctionnels.



    EDIT: D'ailleurs constexpr en C++ ne suffit pas à garantir les contraintes de la programmation fonctionnelle :



    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include<iostream>
    
    
    constexpr int foo() {
    
            int tab[10] = {10};
    
            return tab[-1];
    }
    
    
    int main() {
    
            std::cout << foo() << std::endl;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    $ g++ main.cpp && ./a.out
    22082
    $ g++ main.cpp && ./a.out
    22051

    Tiens, encore mieux :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include<iostream>
    
    int y = 4;
    
    constexpr int foo() {
    
            int x = y;
    
            return x;
    }
    
    
    int main() {
    
            std::cout << foo() << std::endl;
            y = 5;
            std::cout << foo() << std::endl;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    $g++ main.cpp -Wall -Wextra -Wpedantic && ./a.out
    4
    5

    Encore pire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include<iostream>
    
    int y = 4;
    
    constexpr int foo() {
    
            int x = y;
    
            return x;
    }
    
    
    int main() {
    
            std::cout << foo() << std::endl;
            std::cin >> y;
            std::cout << foo() << std::endl;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    $ g++ main.cpp -Wall -Wextra -Wpedantic && ./a.out
    4
    $6
    6

    Je continue
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include<iostream>
    
    int y = 4;
    
    constexpr int foo() {
    
            int & x = y;
    
            ++x;
    
            return x;
    }
    
    
    int main() {
    
            std::cout << foo() << std::endl;
            std::cout << foo() << std::endl;
    }

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    g++ main.cpp -Wall -Wextra -Wpedantic && ./a.out
    5
    6
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  3. #143
    Membre expert
    laisse tomber neckara

    Arrête de pédaler dans le vide, à vouloir des contraintes là où elles ne sont pas nécessaires

    En plus tu découvres le concept et tu es déjà à nous expliquer comment l'appliquer, et toi définir ce qui relève du fonctionnel ou non. En plus à chaque fois tu fais des comparaisons qui n'ont strictement rien à voir. Et tu ne veux pas comprendre que le fonctionnel n'est pas une exigence
    Tu as déjà entendu que comparaison n'est pas raison ?

    Alors pour info :
    Lisp in C's Clothing

    JavaScript's C-like syntax, including curly braces and the clunky for statement, makes it appear to be an ordinary procedural language. This is misleading because JavaScript has more in common with functional languages like Lisp or Scheme than with C or Java. It has arrays instead of lists and objects instead of property lists. Functions are first class. It has closures. You get lambdas without having to balance all those parens.
    https://www.crockford.com/javascript...avascript.html
    Ce texte de Crockford date de 2001, je peux remonter à Brian Eich si tu veux et tu verras comment lui-même définit ce langage qu'il a créé

    J'ai déjà cité des bibliothèques comme underscore ou lodash ici, underscore a 10 ans, lodash 7 et une partie dédiée directement à la programmation fonctionnelle. Ramda a 6 ans : https://ramdajs.com/.
    A ma connaissance mais je n'en suis pas sur, des frameworks comme React ou Angular sont codés en style fonctionnel.

    Ce que tu découvres fait le buzz depuis des années dans des univers langagiers qui sont vivants, et dynamiques. Les mecs attendant pas les contraintes, ils prennent le bon et l'appliquent. Libre à toi de l'utiliser ou pas, de comprendre ce qui les motivent, de basculer ou non, et de manière progressive ou non.

    Des mecs comme Eric Elliot en parle depuis des années sur Medium :
    https://medium.com/javascript-scene/...y-62366b432308
    https://medium.com/@_ericelliott

    C'est pas dur à comprendre, c'est simple comme bonjour. Pas la peine de rendre ces trucs imbitables avec des algos hautement inutiles et hautement décorrélés des besoins quotidiens, de blablater sur la théorie, alors que ça coute rien d'essayer quelque part dans un de tes programmes, pourvu que le langage s'y prête.

    Moi je l'ai fait en Jscript sur un codebase ASP de plusieurs milliers de lignes et de plus de 20 ans d'ancienneté pour quelques rares parties subsistantes, ça n'a rien d'extraordinaire. Moi je te parle du réel, de ce que c'est que de faire vivre du code sur le long terme, de faire vivre et évoluer une application.

  4. #144
    Expert éminent sénior
    Citation Envoyé par fredoche Voir le message
    Arrête de pédaler dans le vide, à vouloir des contraintes là où elles ne sont pas nécessaires
    Ne dit pas que tu fais du fonctionnel, si tu te contentes de faire de l'impératif qui ne fait que ressembler à du fonctionnel.
    C'est juste une question de définitions, c'est comme l'agilité: il ne te suffit pas d'affirmer en faire pour en faire.

    Il n'y a rien de mal à suivre les bests practices comme éviter les effets de bords, mais ce n'est pas pour autant que tu fais du fonctionnel.

    Citation Envoyé par fredoche Voir le message
    En plus tu découvres le concept et tu es déjà à nous expliquer comment l'appliquer, et toi définir ce qui relève du fonctionnel ou non.
    Ben écoute, les pages Wikipédia sont bien claires à ce sujet, ensuite, je ne découvre pas non plus la programmation ou la gestion de projet...

    Citation Envoyé par fredoche Voir le message
    Moi je te parle du réel, de ce que c'est que de faire vivre du code sur le long terme, de faire vivre et évoluer une application.
    Oui, de réel, de long terme, et pas un sou pour faire le minimum d'évaluation syntaxiques/statiques pour justement garantir que ton application n'ai pas d'erreurs évidentes pour un coût quasi-nul.

    C'est sûr que de mettre en place des hooks sur gits, ça te prends à peine... aller 1 jours, pour ensuite te durer une décennie à être utilisé par toute la boîte. Pour derrière te faire gagner un temps fou. C'est sûr que c'est pas le "réel" de la vie vraie. Le "réel" de la vie vrai, c'est que t'as des bugs, des erreurs, des collègues, des stagiaires plus ou moins compétents, mais un client qui ne veut pas de bugs. Et qu'il ne suffit pas de décréter pour que ça se fasse, si tu ne vérifies pas, tu n'as aucune garantie, c'est aussi simple que cela.

    Le "réel" de la vie vrai, c'est que "regarder avec les yeux", ça ne suffit pas et vaut pas grand chose, d'où l'existence d'outils pour le faire à ta place, qui prend quelques millième de secondes pour vérifier ton code et repérer les erreurs les plus évidentes. Le "réel" de la vie vrai, c'est que tu vas prendre les outils adaptés à tes besoins/contraintes.

    Si t'as besoin de faire du fonctionnel, tu prends les outils adaptés, et au pire t'organises ta chaîne de compilation, ou tu fais des bindings. Si t'as pas besoin du fonctionnel, si t'as pas besoin de ses garantis, et que c'est juste des bests practices "éviter effets de bords", ben tu fais pas du fonctionnel, tu fais de l'impératif. T'auras toujours quelques effets de bords non-explicités à gauche et à droite (donc pas du fonctionnel), mais tu auras essayé d'en limiter l'usage.


    Quand je code j'évite au maximum les effets de bords, ce n'est pas pour autant que je clame faire du fonctionnel.
    Et d'ailleurs c'est ce qu'on apprend à nos étudiants : fonctions de calculs = pas d'effets de bords, fonctions d'affichages = juste afficher, pas les deux en même temps.
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  5. #145
    Expert éminent sénior
    Citation Envoyé par fredoche Voir le message
    (.../...)Personne ne te demande de revérifier ces millions de lignes, et c'est pas demain la veille que tu vas prendre une codebase de quelques milliions de lignes pour la passer au TDD.
    Comme évoqué précédemment ça se fait progressivement.
    En effet.

    Là ou je suis, un dev arrivé il y a trois ans a réussi à convertir une équipe au TDD. Nous à la qualité, on a évidemment poussé très fort derrière lui. Mais on a 30 ans d'existant, avec un niveau de qualité souvent exécrable. Est-ce que nous allons exiger de tout ce fatras immonde - mais qui marche - d'être en TDD? Non. Ca n'aurait aucun sens. Ils ne mettent du TDD que là ou ils passent, ce qui reste une portion infime du code.

    Si demain ils décident de faire du fonctionnel(hypothèse douteuse, vu le langage utilisé, et vu leur culture, ils vont plus vers du tout objet, mais admettons), ce sera sur des petits trucs, des features additionnelles. Genre un calcul comptable spécifique au marché Français, et qu'il faille refaire. Bon, ils vont faire le truc en fonctionnel, il vont le farcir de TDD.....sans préjuger du reste. Le reste va rester la merde innommable dont les clients sont ravis depuis 30 ans. En outre, vu les contraintes techniques, ils vont certainement utiliser des langages qui ne sont pas les plus purs fonctionnellement(je passe les détails, on est pas sur des technologies mainstream, on a des contraintes particulières).

    Une base de code, ça vit, ça grandit, ça s'améliore. Par petites touches. Vouloir la perfection du fonctionnel, c'est gentillet pour des projets nouveaux de petite taille. Quand on arrive(ce qui est le cas dans 99% des entreprises) sur un monstre existant, ben on compose. Parce-que le monstre, il fonctionne, et c'est lui qui paye nos salaires. Ce qui rend les approches puristes particulièrement dangereuses. Il faut continuer à faire du business, pendant ce temps-là. Les bonnes pratiques, c'est sur les petits bouts qu'on rajoute ou qu'on maintient - si tant est qu ça aie un sens - si on me donne deux heures pour une maintenance mineures sur un monstre de 36,000 lignes, je ne vais pas m'amuser à le découper en morceaux de taille plus acceptables - même si c'est ce qu'il faudrait faire, en fait - ça n'aurait pas de valeur ajoutée business immédiate - après, si c'est un petit truc mal écrit et dont il faut modifier le fonctionnement, ou si on a un budget refonte, il ne faut surtout pas se gêner.
    Les 4 règles d'airain du développement informatique sont, d'après Michael C. Kasten :
    1)on ne peut pas établir un chiffrage tant qu'on a pas finalisé la conception
    2)on ne peut pas finaliser la conception tant qu'on a pas complètement compris toutes les exigences
    3)le temps de comprendre toutes les exigences, le projet est terminé
    4)le temps de terminer le projet, les exigences ont changé
    Et le serment de non-allégiance :
    Je promets de n’exclure aucune idée sur la base de sa source mais de donner toute la considération nécessaire aux idées de toutes les écoles ou lignes de pensées afin de trouver celle qui est la mieux adaptée à une situation donnée.

  6. #146
    Expert éminent sénior
    Je vais quand même re-préciser, car je pense que cela a mal été compris.

    Je n'ai pas dit que tout le code devait être en fonctionnel, mais que les parties qui doivent l'être doivent juste avoir la garantie de l'être, i.e. via des vérifications à minima syntaxiques.
    Qu'on ne me fasse pas dire ce que je n'ai jamais dit.



    Ensuite, même si on ne va pas retoucher tout le fatras du code existant, cela n'empêche pas de lancer quelques vérifications syntaxiques de bases ou de re-indenter.
    Cela peut être l'occasion de corriger assez rapidement et gratuitement certains des warnings très bêtes, mais qui pourraient exploser à la figure à tout moment.

    Pas la peine en effet de se lancer dans un refactoring complet. Mais si prendre e.g. 1 journée pour corriger quelques trucs stupides pour gagner derrière 1 semaine de débug parce qu'un truc plante sans qu'on comprenne pourquoi, et à devoir se taper la lecture et la compréhension d'un plat de spaghetti avec des comportements non-déterministes. et/ou indeterminés.
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  7. #147
    Membre expert
    Citation Envoyé par Neckara Voir le message
    Ne dit pas que tu fais du fonctionnel, si tu te contentes de faire de l'impératif qui ne fait que ressembler à du fonctionnel.
    C'est juste une question de définitions, c'est comme l'agilité: il ne te suffit pas d'affirmer en faire pour en faire.

    Il n'y a rien de mal à suivre les bests practices comme éviter les effets de bords, mais ce n'est pas pour autant que tu fais du fonctionnel.
    Je ne vais pas me battre plus loin, ton entêtement à avoir raison confine à la bêtise.

    Tu es même incapable de voir des projets qui ont une décennie dans des langages qui n'ont pas ces vérifications. Tu iras leur expliquer que ce n'est pas du fonctionnel, que c'est juste de l'affirmation

    Et moi je sais pourquoi on a désormais des décennies de retard sur le génie logiciel, c'est une problématique d'arrogance.

  8. #148
    Expert confirmé
    Citation Envoyé par Neckara Voir le message
    Le "réel" de la vie vrai, c'est que "regarder avec les yeux", ça ne suffit pas et vaut pas grand chose, d'où l'existence d'outils pour le faire à ta place, qui prend quelques millième de secondes pour vérifier ton code et repérer les erreurs les plus évidentes. Le "réel" de la vie vrai, c'est que tu vas prendre les outils adaptés à tes besoins/contraintes.

    Si t'as besoin de faire du fonctionnel, tu prends les outils adaptés, et au pire t'organises ta chaîne de compilation, ou tu fais des bindings.
    Pour ma part, je suis d'accord qu'il vaut mieux avoir des outils que ne pas en avoir. Mais, en ce moment, je travaille sur du code en Python et je ne connais pas d'outil pour vérifier statiquement, en Python, quelles fonctions sont pures ou non pures. Or, dans mon contexte, il ne serait pas raisonnable de réécrire des parties de ce programme en Haskell.

    Citation Envoyé par Neckara Voir le message
    Si t'as pas besoin du fonctionnel, si t'as pas besoin de ses garantis, et que c'est juste des bests practices "éviter effets de bords", ben tu fais pas du fonctionnel, tu fais de l'impératif. T'auras toujours quelques effets de bords non-explicités à gauche et à droite (donc pas du fonctionnel), mais tu auras essayé d'en limiter l'usage.


    Quand je code j'évite au maximum les effets de bords, ce n'est pas pour autant que je clame faire du fonctionnel.
    Et d'ailleurs c'est ce qu'on apprend à nos étudiants : fonctions de calculs = pas d'effets de bords, fonctions d'affichages = juste afficher, pas les deux en même temps.
    Le concept de programmation fonctionnelle existait déjà avant que des langages vérifient à la compilation quelles fonctions sont pures ou non pures. Historiquement, le langage de programmation de haut niveau le plus ancien dans lequel on pouvait faire de la programmation fonctionnelle était Lisp. Or, Lisp est un langage dynamiquement typé qui ne garantit pas que l'on utilise un style fonctionnel.
    Si on privilégie les fonctions de première classe et l'immuabilité et qu'on évite les effets de bord, il est adapté de dire qu'il s'agit d'un style fonctionnel. Si on impose dans la définition que les contraintes doivent être vérifiées par un outil, alors cela ne colle pas à l'usage historique du terme.

  9. #149
    Expert éminent sénior
    Citation Envoyé par fredoche Voir le message
    Tu es même incapable de voir des projets qui ont une décennie dans des langages qui n'ont pas ces vérifications. Tu iras leur expliquer que ce n'est pas du fonctionnel, que c'est juste de l'affirmation
    Si tu fais du fonctionnel sur un projet depuis une décennie sans avoir ajouté ne serait-ce que des bêtes vérifications syntaxiques via un git hook, des trucs qui peuvent être vraiment tout con, qui peuvent être fait en une demi-journée (pour un projet d'une décennie c'est rien) e.g. exiger des "const" sur les paramètres de la fonction, et en C++ maintenant exiger constexpr, ou encore plus con, définir des macros pre-processeurs, ou une étape de pré-compilation.

    Là oui, en effet, je comprends pourquoi certaines personnes ont des décennies de retards sur le génie logiciel.


    Et puis c'est quoi le problème de ne pas faire du fonctionnel ?
    C'est une honte ? Il faudrait s'en cacher ?


    Et puis si c'est pour faire une décennie de dev en fonctionnel sur un langage non-adapté, à un moment, on peut commencer à se poser quelques questions.
    C'est pas comme s'il existait multitudes de solutions pour régler le problème de manière très peu coûteuses. Encore faut-il être à jour avec son temps.

    C'est aussi un peu con de faire du pseudo-fonctionnel pendant des décennies en se privant justement des avantages du fonctionnel…
    De ne pas comprendre pourquoi tu as un bug bizarre alors que toutes tes fonctions sont censées être sans effets de bords, pour y passer des semaines dessus parce que le code d'il y a 9 ans est illisible, et que l'impossible se produit, tes fonctions ne respectant pas leur "contrats". Ou que tes fonctions ont passées des vérifications tests unitaire ou carrément en méthode formelle assumant l'absence d'effets de bords, sauf qu'à cause d'un effet de bord, tu plantes un train dans le décors.

    Et parce que tu auras eu la flemme de perdre quelques heures à peine à réfléchir, tu te payeras des conséquences bien plus coûteuses.
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  10. #150
    Expert éminent sénior
    Citation Envoyé par Pyramidev Voir le message
    Pour ma part, je suis d'accord qu'il vaut mieux avoir des outils que ne pas en avoir. Mais, en ce moment, je travaille sur du code en Python et je ne connais pas d'outil pour vérifier statiquement, en Python, quelles fonctions sont pures ou non pures. Or, dans mon contexte, il ne serait pas raisonnable de réécrire des parties de ce programme en Haskell.
    Et ? En quoi est-ce un problème de ne pas pouvoir les réécrire ?

    Soit tu dis que tu continues à faire du Python en fonctionnel-like sans assumer par défaut l'absence d'effets de bords (donc pas du fonctionnel), soit tu codes les nouvelles parties en haskell en les invoquant en Python ou l'inverse.

    Après, il faut savoir pourquoi tu as besoin du fonctionnel.
    Si c'est pour des garanties de sécurité du code, tu seras bien obligé d'utiliser les outils appropriés, sinon ces garanties, tu ne les auras pas.
    Sinon c'est pas un drame de ne pas faire du fonctionnel et de faire de l'impératif avec une syntaxe fonctionnel-like.


    Faut aussi voir à long terme, surtout sur des gros projets, si c'est juste augmenter la dette technique d'années en années pour se retrouver face au mur dans 5 ans…
    C'est comme si tu faisais de l'OO en assembleur… au bout d'un moment ça risque de devenir problématique.


    EDIT: D'ailleurs je me demandes même si l'immuabilité est vraiment possible dans les langages non-adaptés au fonctionnel tout en conservant des performances acceptables pour des opérations sur de grandes données.
    Avec std::move de C++, ça doit pouvoir se faire (et encore…), mais sur d'autres langages j'ai peur que les copies incessantes soient assez rédhibitoires. Dans les langages adaptés, j'ai compris qu'ils avaient quelques optis pour éviter ce genre de comportement, mais j'ai de grands doutes que les langages non-adaptés le fassent eux-aussi.

    Citation Envoyé par Pyramidev Voir le message
    Le concept de programmation fonctionnelle existait déjà avant que des langages vérifient à la compilation quelles fonctions sont pures ou non pures.
    Un concept n'est pas forcément entièrement implémenté correctement dès ses débuts.

    Citation Envoyé par Pyramidev Voir le message
    Si on privilégie les fonctions de première classe et l'immuabilité et qu'on évite les effets de bord, il est adapté de dire qu'il s'agit d'un style fonctionnel.
    Sauf qu'en faisant cela, dans les faits, la réalité vrai de la vie, tu te retrouveras avec des effets de bords non-explicités, donc contrevenant au principe du fonctionnel, et potentiellement t'empêchant d'utiliser certains outils dédiés assumant l'utilisation du fonctionnel.

    Tu te retrouves à faire de l'impératif en sommes.




    Tu n'es pas obligé stricto sensu de faire de la vérification, mais dans les faits si tu ne le fais pas, tu pourras être sûr qu'à un endroit dans ton code, tu aurais un effet de bord non-explicite.

    EDIT: Vous faites bien du fonctionnel non pas pour le plaisir de faire du fonctionnel, mais bien parce que cela vous apporte quelque chose du fait de l'absence ou de l'explicitation des effets de bords ?
    Si vous avez un effet de bord non-explicite vous perdez ce quelque chose (e.g. exécution parralèle, preuves formelles, compréhension de la fonction à son prototype, fonctions ddéterministes, etc.).
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  11. #151
    Membre habitué
    Citation Envoyé par Neckara Voir le message
    EDIT: D'ailleurs je me demandes même si l'immuabilité est vraiment possible dans les langages non-adaptés au fonctionnel tout en conservant des performances acceptables pour des opérations sur de grandes données.
    Avec std::move de C++, ça doit pouvoir se faire (et encore…), mais sur d'autres langages j'ai peur que les copies incessantes soient assez rédhibitoires. Dans les langages adaptés, j'ai compris qu'ils avaient quelques optis pour éviter ce genre de comportement, mais j'ai de grands doutes que les langages non-adaptés le fassent eux-aussi.

    Cela dépend de ce dont on parle. Les Map d’Ocaml sont immuables, mais il n’y a rien dans le code qui empêche une implémentation en Java. Il s’agit d’un arbre binaire codé simplement en allouant de nouveaux noeuds à la place de modifier les anciens. En C++, faute de garbage collector, cela poserait problème.

    Le std::move ne fait pratiquement* que déplacer un pointeur d’un objet à l’autre en annulant le pointeur de l’objet source. Le principe est de transférer une valeur par pointeur (type tableau, arbre ou que sais-je), en gardant une seule référence pour éviter des désallocations non voulues. N’importe quel langage avec garbage collector n’en n’a pas besoin.

    *pratiquement.... de façon formelle, c’est l’objet qui définit la sémantique de std::move.

    Faire des objets non muables est assez facile en orienté objet... ainsi les String Java sont immuables. Il suffit de ne coder que des méthodes qui lisent l’état d’un objet et des constructeurs qui en créent avec un état figé. Il faut aussi éviter de retourner un champ muable de l’objet en question. (Les langages fonctionnels ont l’avantage d’avoir des listes immuables... en Python, il faudrait retourner des tuples et non des listes). On peut évidemment retourner des valeurs copiées (nombres, caractères, énumérés...)

  12. #152
    Expert éminent sénior
    Citation Envoyé par floyer Voir le message
    Faire des objets non muables est assez facile en orienté objet... ainsi les String Java sont immuables.
    Ce qui est une des raisons pour laquelle Java a un StringBuilder.

    Dès que tu as un tableau immuable, chaque modification nécessite normalement de recopier l'ensemble du tableau avec la modification a effectuer (e.g. ajout/retrait/modification), et dans notre cas, avec des deeps copies. Cela a un coût non-négligeable qui peut très vite devenir rédhibitoire.

    Les langages adaptés s'en sortent avec de l'évaluation paresseuses et d'autres techniques, mais les autres langages, surtout les compilés, … j'ai de très gros doutes.
    Même si tu t'épargnes l'appel système (malloc/free, new/delete) grâce à un GC, tu auras toujours le coût d'une copie, à moins que ton langage soit capable de comprendre que l'original de ta copie sera détruit avant d'être réutilisé par la suite… pas sûr qu'il y arrive à tous les coups.

    Citation Envoyé par floyer Voir le message
    Il suffit de ne coder que des méthodes qui lisent l’état d’un objet et des constructeurs qui en créent avec un état figé.
    Justement, c'est hypercoûteux pour le coup.
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  13. #153
    Expert confirmé
    Je rattrape la discussion en cours de route, donc désolé si mes commentaires sont un peu décousus.

    Pour l'histoire des copies mémoires peu performantes, encore une fois, je pense que c'est un faux problème au vu des optimisations que peut faire le compilateur. J'en veux pour preuve le fameux détecteur de spam de facebook, codé en haskell : https://engineering.fb.com/security/...-with-haskell/. Bon ok, il a été codé par des gens très compétents mais ça prouve quand même le potentiel de performance du fonctionnel.

    Pour l'histoire de la vérification des fonctions pures, je suis plutôt d'accord avec Neckara : un outil de vérification est quasiment indispensable. On est tous humain et on fait tous des erreurs donc un outil qui vérifie ce qu'on fait est vraiment d'une très grande aide. Après c'est pas forcément facile à mettre en place, donc juste une bonne pratique, c'est déjà quelque chose.

    Pour l'histoire de la dette technique sur les bases de code existantes, en théorie oui ce serait mieux de tout convertir aux bonnes pratiques en claquant des doigts mais en pratique c'est quand même très risqué et il vaut souvent mieux y aller doucement et progressivement.

    Sinon pour compléter l'histoire des monades, on peut ajouter que c'est un concept qui existe aussi dans d'autres langages comme rust et javascript (via les "then"), même si elles ne sont pas aussi omniprésentes qu'en haskell.

  14. ###raw>post.musername###
    Membre habitué
    Citation Envoyé par Neckara Voir le message

    Justement, c'est hypercoûteux pour le coup.
    Je ne vois pas ce qui différentie fondamentalement les langages fonctionnels des autres de ce point de vue.

    Une liste (immuable) est typiquement une cellule de deux pointeurs (car . cdr) pour prendre la convention Lisp... rien d’inaccessible pour un langage procédural. Un

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class ListNode<T> {
    private:
      T car;
      ListNode<T> cdr;
    }
    Suffit à définir les champs d’une liste générique en Java. Le caractère immuable viendra du choix des méthodes. Et si c’est plus lourd, c’est à cause d’un overhead des objets Java (en C++ ce serait juste deux pointeurs en mémoire).

    Le problème avec les noeuds (liste chaînées, arbres,...), est qu’un usage fonctionnel (on ne modifie aucun noeud mais on réutilise d’autres noeuds) va vite créer des noeuds référencés de multiple fois, d’autres non, etc. Ce qui est inextricable sans GC. C’est pour cela que j’ai privilégié Java.

    EDIT : Je viens de penser à une optimisation de Ocaml. En mode compilé, il va coder un entier n sous la forme 2n+1. Ainsi, ce qui est pair est un pointeur, ce qui est impair est un nombre. Cf https://v1.realworldocaml.org/v1/en/html/memory-representation-of-values.html. Cela aide le GC en limitant l’empreinte mémoire.

    Réciproquement, Ocaml a des Array.... mais il sont muables... et un let a2=a1 te donnera une deuxième référence sur le même tableau, pas de copy on write etc.

    Les Array de Haskell sont immuables, du coup, je ne sais si et comment il optimise une écriture d’un unique élément (dans une copie). Il faudrait voir s’il n’y a pas bêtement une copie, ce qui est à la portée de tout langage procédural. (L’interface des Array privilégie des changements par lot probablement pour éviter de multiplier les copies. L’ordre de copie avec modification, noté \\ prend en paramètre une liste de changements : paires (indices, nouvelle valeur). Il n’y a pas de notation pour changer un unique élément sauf à utiliser une liste avec un seul couple)
      1  0

  15. #155
    Expert éminent sénior
    Citation Envoyé par SimonDecoline Voir le message
    Pour l'histoire des copies mémoires peu performantes, encore une fois, je pense que c'est un faux problème au vu des optimisations que peut faire le compilateur. J'en veux pour preuve le fameux détecteur de spam de facebook, codé en haskell : https://engineering.fb.com/security/...-with-haskell/. Bon ok, il a été codé par des gens très compétents mais ça prouve quand même le potentiel de performance du fonctionnel.
    Pour un compilateur fait pour le fonctionnel, oui, mais je parle des langages non-adaptés au fonctionnel. Je peux t'assurer qu'en C++, ça risque d'être plus compliqué et qu'il y aura des cas où le compilateur ne pourra de toute façon pas faire une telle optimisation.

    Citation Envoyé par SimonDecoline Voir le message
    Après c'est pas forcément facile à mettre en place, donc juste une bonne pratique, c'est déjà quelque chose.
    Tu ne peux pas faire 2-3 trucs assez rapidement avec des outils de vérification de grammaires ? Même si cela reste très basique (e.g. imposer const/constexpr en C++) ?
    Ou au pire à à la bourrin via des regex un peu dégueulasses ?

    Citation Envoyé par SimonDecoline Voir le message
    Pour l'histoire de la dette technique sur les bases de code existantes, en théorie oui ce serait mieux de tout convertir aux bonnes pratiques en claquant des doigts mais en pratique c'est quand même très risqué et il vaut souvent mieux y aller doucement et progressivement.
    Justement, j'ai bien précisé de corriger quelques warnings, pas de tous les corriger.

    Par exemple si tu as un warning sur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    try{
         file = open('/home/stagiares/kevin/project/dist/meta.txt'); // chemin absolu.
    } catch(e) {} // no instructions in catch.
    Ça peut vraiment valoir le coup de rajouter la gestion de l'erreur dans le catch (e.g. une entrée dans un log.err), de remplacer le chemin absolu en chemin relatif, puis de vérifier qu'on a bien le fichier sur son disque.
    Sinon c'est le coup à voir le programme planter et à passer des heures à comprendre ce qui ne va pas pour s'apercevoir la bourde du stagiaire (et ça arrive assez fréquemment).

    Citation Envoyé par floyer Voir le message
    Je ne vois pas ce qui différentie fondamentalement les langages fonctionnels des autres de ce point de vue.
    Les autres langages ont beaucoup de containeurs qui ne sont pas adaptés à cela, e.g. const std::vector en C++.
    Et si tu commences à passer via listes, tu n'as pas la garantie que les éléments alloués seront consécutifs en mémoire, idem, niveau perfs, c'est pas tip top, tu vas faire beaucoup d'aller-retours entre les caches. À moins d'avoir une allocation (ou un GC) intelligent.

    Tu risques aussi de ne plus avoir d'accès en O(1), et même en passant par une map pour les index, tu seras au mieux en O(logn).
    Pour des grosses listes, c'est pas rien.

    Ça dépend ce que tu fais, mais si tu fais beaucoup de lecture/modifications via un index donné, sur une grande liste, ça peut faire mal.

    Citation Envoyé par floyer Voir le message
    Réciproquement, Ocaml a des Array.... mais il sont muables... et un let a2=a1 te donnera une deuxième référence sur le même tableau, pas de copy on write etc.
    C'est quand même problématique d'utiliser un conteneur mutable dans un paradigme où justement on est censé au moins les éviter, à défaut de les bannir complètement, non ?

    Citation Envoyé par floyer Voir le message
    Le problème avec les noeuds (liste chaînées, arbres,...), est qu’un usage fonctionnel (on ne modifie aucun noeud mais on réutilise d’autres noeuds) va vite créer des noeuds référencés de multiple fois, d’autres non, etc. Ce qui est inextricable sans GC. C’est pour cela que j’ai privilégié Java.
    On pourrait se débrouiller avec des pointeurs intelligent, ou en simuler, mais en effet, ça demande plus d'efforts.

    Sinon oui je vois, vu que c'est constant, on n'a pas toujours besoin de copier les nœuds sur des structures chaînées. Cela limite en effet les copies, mais ne les fait pas disparaître. Les insertions en queues deviennent en revanche super-coûteuses car il faudra tout recopier, contrairement aux insertions en tête.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    L1 -> a -> b -> c
    L2 -> a -> b -> c
    // si insertion de d en tête dans L2 :
    L2 -> d -> a -> b -> c
    // si insertion de d en queue dans L2 :
    L2 -> a' -> b' -> c' -> d // car il faut modifier c pour ajouter d (i.e. copier c).
    Citation Envoyé par floyer Voir le message
    Les Array de Haskell sont immuables, du coup, je ne sais comment il optimise un écriture d’un unique élément (dans une copie). Peut-être un coup de l’évaluation paresseuse. Et il faudrait voir s’il n’y a pas bêtement une copie, ce qui est à la porter de tout langage procédural.
    Vu que tout est censé être immuable, le compilateur peut aussi potentiellement réécrire des trucs pour optimiser le code.
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  16. #156
    Membre habitué
    Citation Envoyé par Neckara Voir le message


    Et si tu commences à passer via listes, tu n'as pas la garantie que les éléments alloués seront consécutifs en mémoire, idem, niveau perfs, c'est pas tip top, tu vas faire beaucoup d'aller-retours entre les caches. À moins d'avoir une allocation (ou un GC) intelligent.

    Tu risques aussi de ne plus avoir d'accès en O(1), et même en passant par une map pour les index, tu seras au mieux en O(logn).
    Pour des grosses listes, c'est pas rien.

    Ça dépend ce que tu fais, mais si tu fais beaucoup de lecture/modifications via un index donné, sur une grande liste, ça peut faire mal.

    Oui, une liste chaînée s’indexe en un temps en O(n).... quelque soit le langage (fonctionnel ou non).

    Je rappelle qu’un liste en Lisp est définie par un ensemble de cellules (car . cdr) liées entre elles via le pointeur cdr. En Ocaml, de même, plus exactement type 'a list = Nil | Cons of 'a * 'a list, ce qui est similaire.

    Du coup, je ne vois pas ou tu veux en venir. Privilégier les langages qui mettent en avant les tableaux plutôt que les listes ?

    Quand au caractère contigu des cellules, il dépend du GC qui souvent déplace les structures en mémoire.

    Vu que tout est censé être immuable, le compilateur peut aussi potentiellement réécrire des trucs pour optimiser le code.
    Il n’y a pas 36 façon de copier un tableau. Mutable ou non. Le plus simple est un memcpy qui se code en assembleur (rep movsb). Et on est obligé de faire cette copie physique si l’on doit changer une ou quelques valeurs. Je ne vois pas de structure de données efficaces (renvoi d’une copie altéré en O(1), lecture de cette copie en O(1)), même en tenant compte du caractère imuable. Et si une telle structure existait, rien n’interdirait de la coder en un langage procédurale (tout comme le langage fonctionnel est converti en assembleur qui est procédural).

    Enfin, les compilateurs C, C++, Java sont autorisés à considérer constant les variables qui leur sont soumis et optimiser en conséquence. Un a=true; while(a) { } peut être une boucle sans fin même si a est modifié par un autre thread. Le mot clé volatile sert pour éviter cette «*optimisation*».

  17. #157
    Expert confirmé
    @Neckara : en C++, les fonctions constexpr sont très contraignantes : on ne peut même pas faire d'allocation dynamique avec. Pour contrôler en C++ qu'une fonction est non pure en autorisant des contraintes un peu plus souples, il faudrait une nouvelle fonctionnalité pour le langage, par exemple l'équivalent de l'attribut pure en langage D.

  18. #158
    Expert éminent sénior
    Citation Envoyé par floyer Voir le message
    Du coup, je ne vois pas ou tu veux en venir. Privilégier les langages qui mettent en avant les tableaux plutôt que les listes ?
    Disons que cela dépend toujours des besoins, mais ça serait bête à la presque fin d'un projet de s'apercevoir qu'une implémentation sous forme de liste est trop coûteuse en terme de complexité et qu'il faudrait passer par un tableau.

    Autant je n'ai pas trop de problèmes pour langages compilés/interprétés, autant là, ça joue sur les complexités.
    Personnellement, ça me semble hautement rédhibitoire, bon c'est aussi parce que je fais surtout des calculs sur des grandes quantités de données.

    Et si c'est faire du fonctionnel pour derrière utiliser des tableaux mutables quand cela m'arrange, contrevenant un peu au principe du fonctionnel, je n'en vois plus vraiment l'intérêt. Autant directement passer à l'impératif, quitte à conserver quelques bonnes pratiques.

    Citation Envoyé par floyer Voir le message
    Il n’y a pas 36 façon de copier un tableau. Mutable ou non. Le plus simple est un memcpy qui se code en assembleur (rep movsb). Et on est obligé de faire cette copie physique si l’on doit changer une ou quelques valeurs. Je ne vois pas de structure de données efficaces, même en tenant compte du caractère imuable. Et si une telle structure existait, rien n’interdirait de la coder en un langage procédurale (tout comme le langage fonctionnel est converti en assembleur qui est procédural).
    Disons qu'en impératif, tu n'as pas besoin de copier ton tableau à chaque modifications vu que l'impératif autorise la mutation alors que le fonctionnel est censé l'interdire.

    Citation Envoyé par floyer Voir le message
    Enfin, les compilateurs C, C++, Java sont autorisés à considérer constant les variables qui leur sont soumis et optimiser en conséquence. Un a=true; while(a) { } peut être une boucle sans fin même si a est modifié par un autre thread. Le mot clé volatile sert pour éviter cette «*optimisation*».
    Oui, mais le compilateur e.g. Haskell fait certainement plus de choses du fait des hypothèses supplémentaires qu'il peut poser du fait de l'absence d'effets de bords.
    Il se peut aussi qu'il fasse de l'évaluation paresseuse, par exemple, si seulement le 5ème élément de ton tableau est utilisé, il pourrait intelligemment ne calculer que ce dernier et ne pas calculer les autres. Vu qu'il n'y a pas d'effets de bords, il pourrait se le permettre.
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  19. #159
    Expert éminent sénior
    Citation Envoyé par Pyramidev Voir le message
    @Neckara : en C++, les fonctions constexpr sont très contraignantes : on ne peut même pas faire d'allocation dynamique avec.
    En effet. On peut allouer sur la pile au lieu d'allouer sur le tas, cependant cela nous obligerait à tout copier tout le temps.


    Sauf que le C++20 semble venir à notre secours (je suis en train de regarder la vidéo) :

    EDIT: ça semble vraiment cool les constexpr en C++20.
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  20. #160
    Membre habitué
    Pour compléter mon propos, modifier une seule valeur dans un grand Array Haskell prend beaucoup de temps... signe que Haskell recopie le tableau entier. On ne peut pas vraiment parler d’optimisation lié à l’imputabilité.