Sur la nécessité en informatique

a marqué ce sujet comme résolu.

Ce sujet fait suite à la conversation commencée dans ce sujet.

Pour rappel, voici le dernier message de la précédente discussion :

Il n’en reste pas moins que, à ma connaissance, il n’existe pas de langage interprété comparable, en matière de performance et de charge serveur (puisque nous parlons d’une utilisation sur un serveur), à un "bon" langage compilé.

Certes mais en cherchant cette comparaison et en te reposant uniquement sur les performances et la charge serveur, je pense que tu occultes d’autres contraintes qui naissent de la nécessité dont tu parles plus bas (discours sur lequel je te rejoins).

Aujourd’hui nous vivons une époque où tout le monde n’a pas les mêmes besoins : une phrase que l’on entend souvent est "on n’est pas google/facebook/tinder/netflix" : avant de se soucier de la charge des serveurs, la nécessité est d’aboutir rapidement à un produit stable et utilisable. Bien souvent, les critères qui auront le plus d’impact dans le choix d’une technologie pour développerune nouvelle application, seront son écosystème et le time to delivery, à savoir le temps écoulé entre l’idée initiale et son déploiement en production.

Dans ces conditions, la nécessité étant le fonctionnel (le service rendu) avant les performances, le choix le plus intelligent est bien souvent celui du langage qui pose le moins de barrières, que celles-ci prennent la forme d’une courbe d’apprentissage ou du developpement d’une bibliothèque qui n’existe pas encore. Et les technologies connues pour lever ces barrières sont généralement Python et Javascript, là où la "nécessité" pousserait… Rust, entre autres.

Ceci étant dit, je suis d’accord avec toi quand tu considères, par exemple, un langage comme Go comme répondant bien à la nécessité, car il offre un excellent compromis entre performances et time to delivery (on peut apprendre à l’utiliser en une après-midi, il est encore plus batteries included que Python, et son écosystème est déjà méga-riche) : la seule chose qui lui manque, finalement, c’est la popularité, mais ça commence enfin à venir (si je me fie au marché de l’emploi, puisque j’ai justement décroché un poste en Go le mois dernier, alors qu’il fallait vraiment fouiller longtemps pour en trouver auparavant).

PS : je propose qu’on crée un sujet dédié. Vraiment. Pour arrêter le HS ici.

nohar

Je l’occulte, et je ne l’occulte qu’à moitié en réalité.

J’ai la chance de faire de la programmation un simple passe-temps. Je n’en vis pas, je ne souhaite pas en vivre. Je ne souhaite pas en vivre précisément car je sais l’importance du temps dans la programmation.

Il est évident que les choix en informatique sont des conséquences des injonctions du système économique dans lequel nous vivons. Mais c’est aussi pour cela que je faisais mention des enjeux écologiques.

La raréfaction des ressources, la prise de conscience progressive du coût écologique de la technologie (et notamment des parcs de serveurs) montrent qu’il faut absolument repenser notre usage de la technologie et nos pratiques en programmation.

Mais outre cela, ces questions de nécessité se posent même dans le cadre économique : un produit trop lent, un produit incompatible avec des configurations est un handicap majeur. L’exemple le plus probant est l’exemple du jeu vidéo : combien de clients potentiels écartés par de mauvaises pratiques ?

Actuellement, ces pertes sont très largement compensées par le temps gagné. Des usines à gaz comme Laravel ou Ruby on Rails sont abondamment utilisées dans le développement web parce que payer davantage pour un serveur coûte pour l’instant moins cher que passer davantage de temps à programmer.

Par la force des choses, cette logique va avoir une fin à un moment ou à un autre. D’abord parce que les ressources sont amenées à être limitées, et d’autre part car nous sommes à un stade où nous n’arrivons plus à progresser autant qu’il y a vingt ans en matière de puissance brute.

On voit bien, par ailleurs, que ce sont des préoccupations grandissantes : tous les "nouveaux" langages (Nim "efficient, expressive, elegant", Crystal et son "slick as C", Go qui appuie sa rapidité jusque dans son nom et dans son logo, Rust et ses abstractions "sans coût" à la manière du C++) mettent l’accent sur leurs performances, là où la génération précédente insistait sur le confort du programmeur.

Le cas de Rust, que tu cites, est plus délicat. Rust suit le chemin, assez mauvais à mon avis, du C++ moderne. En somme, Rust répond à des problèmes nouveaux en ajoutant des fonctionnalités nouvelles. Rust est peu à peu en train de devenir un langage d’une complexité absurde. Trop d’axiomes et de règles superflus.

Go est à mon sens préférable car il veut se limiter à l’essentiel. Nous sommes donc d’accord pour dire de Go qu’il est un des meilleurs langages actuels pour faire face à cette exigence de nécessité.

Bref : rasoir d’Ockham.

Je l’occulte, et je ne l’occulte qu’à moitié en réalité.

J’ai la chance de faire de la programmation un simple passe-temps. Je n’en vis pas, je ne souhaite pas en vivre. Je ne souhaite pas en vivre précisément car je sais l’importance du temps dans la programmation.

Il est évident que les choix en informatique sont des conséquences des injonctions du système économique dans lequel nous vivons. Mais c’est aussi pour cela que je faisais mention des enjeux écologiques.

Attention, la criticité du temps de développement dépasse de loin les seuls enjeux économiques. Typiquement, on peut très bien se placer dans le contexte d’un logiciel libre et quand même justifier le besoin d’un langage qui accélère le développement.

D’abord, on l’oublie facilement et cette idée n’est pas toujours intuitive, mais les enjeux de l’ingénierie logicielle, de nos jours, sont bien plus humains qu’ils ne sont techniques. Quel que soit le logiciel que l’on développe, sa vocation première est de rendre un service à un être humain. Il est là pour répondre au besoin, très concret, d’un utilisateur qui ne sait pas vraiment ce qu’il veut, et encore moins traduire ce besoin en solution logicielle (et c’est normal, c’est un métier à part entière, et on a bien compris depuis longtemps que tout planifier en amont est une perte de temps). C’est notamment pour cette raison que la plupart des logiciels sont développés de façon itérative : on fait d’abord quelque chose de simple que l’on montre à l’utilisateur, et une fois que l’on a confronté le soft à l’utilisateur, on affine notre compréhension de son besoin, donc on peut ajuster le cap et décider de la direction dans laquelle partir pour la prochaine version.

Du point de vue du développeur, cela se traduit par des cycles de développement courts et un code qui, au début de sa vie, est modifié beaucoup et très souvent, mais aussi beaucoup de code jetable (des prototypes, notamment). Aujourd’hui, je pense qu’on peut avancer sans trop se tromper que ce n’est qu’en s’y prenant de cette façon que l’on peut aboutir à un logiciel utile et adapté à ses utilisateurs.

D’autre part même si l’on rentre dans les considérations écologiques, il faut garder en tête que l’empreinte écologique d’un logiciel dépend en premier lieu de l’échelle à laquelle il est utilisé. Comme je le disais : "on n’est pas google". Sur un site de 2 ans d’âge qui accueille à tout casser 1000 visiteurs réguliers par jour, quelle que soit la technologie employée pour le faire tourner, son empreinte énergétique est de toute façon complètement négligeable devant un dropbox, ou un service comme via navigo, et il en va de même de ses contraintes en performances (si Django ou Rails savent gérer 1000 requêtes par seconde, alors ils suffisent largement). Les problèmes de consommation ne vont donc se poser que du moment où un projet va avoir besoin de passer à l’échelle, or dans la vraie vie, cela ne touche qu’une petite fraction des projets de développement : un sur dix, car les neuf autres projets seront morts avant d’arriver à ce stade, ou bien n’en auront tout simplement jamais besoin.

Bref, il faut vraiment prendre le cycle de vie du logiciel en compte : il ne serait pas du tout raisonnable de se demander si un logiciel est assez léger/performant/eco-friendly tant qu’on ne sait pas encore s’il est utile ou viable.

Par contre, et là-dessus je te rejoins : lorsque l’on en arrive au passage à l’échelle, il convient de remplacer les diverses briques logicielles, en employant cette fois des technologies qui permettent d’aboutir à un code plus léger et (beaucoup) plus performant. Parce qu’une fois que l’on en arrive là, la consommation énergétique du logiciel devient vraiment significative. Et c’est cette étape indispensable qui saute en premier, particulièrement dans l’industrie, le plus souvent pour des raisons économiques, et je le déplore tout autant que toi.

Le cas de Rust, que tu cites, est plus délicat. Rust suit le chemin, assez mauvais à mon avis, du C++ moderne. En somme, Rust répond à des problèmes nouveaux en ajoutant des fonctionnalités nouvelles.

Je pense que tu te trompes lourdement sur ce coup : Rust répond à des problèmes qui ont plus de cinquante ans d’existence, mais en utilisant des notions théoriques modernes. Quand on a un peu de recul et d’expérience dans le développement, on se rend bien vite compte que si Rust est complexe, c’est parce qu’il rend apparente la complexité intrinsèque des problèmes auxquels tous les programmes finissent par être confrontés un jour ou l’autre. En d’autres termes : le compilateur de Rust est une machine à voyager dans le temps, dans le sens où il t’impose de gérer dès la compilation des problèmes auquel le code équivalent en C finirait tôt ou tard par rencontrer en production, sous la forme d’un segfault ou d’un comportement indéterminé (très douloureux à déboguer).

Néanmoins, ce n’est pas non plus une panacée universelle : il est parfaitement adapté pour créer des briques logicielles qui sont critiques prises individuellement, mais il serait tout aussi déraisonnable de généraliser cette approche de la safety à la totalité d’une application, qu’il le serait de porter des gants et lunettes de protection ainsi qu’une armure en kevlar pour faire cuire des pâtes. :)

+3 -0

Hello,

Comme je le disais : "on n’est pas google". Sur un site de 2 ans d’âge qui accueille à tout casser 1000 visiteurs réguliers, quelle que soit la technologie employée pour le faire tourner, son empreinte énergétique est de toute façon complètement négligeable devant un dropbox, ou un service comme via navigo

En ne parlant que l’empreinte énergétique, je suis pas convaincu par l’argument. On n’est pas Google, mais le problème est qu’il y a beaucoup de sites qui ne sont pas Google. Du point de vue du bilan énergétique global (qui est le seul pertinent, en fait), quelle est la contribution de tous les petits sites et est-ce qu’il ne serait pas intéressant de diminuer leur consommation ?

+0 -0

est-ce qu’il ne serait pas intéressant de diminuer leur consommation ?

Cela dépend de son utilisation. Sur un site comme Zeste De Savoir, cela me semble parfaitement overkill. Sur la totalité des blogs "Powered by wordpress", par contre, oui, c’est beaucoup plus intéressant. :)

+0 -0

J’ai l’impression qu’on retombe sur les discussions qu’il y a eu l’automne dernier, quand est paru le billet « Software disenchantment ». Cf celles sur ce site ou sur LinuxFR pour des débats en français.

Depuis… rien n’a changé. Parce que ça n’est pas rentable de changer.

Salut,

Je me permets de rajouter ceci : dans le cas de ZdS, il ne me semble pas impossible que le temps de développement et de test représente une partie non-négligeable de la consommation d’énergie du site (les gentils bénévoles qui font ça sont des ordis avec des écrans qui consomment a priori bien plus qu’un serveur convenable).

(EDIT : dans les liens donnés par SpaceFox on retrouve cette idée, peut-être moins orientée énergie, enfin toujours est-il que c’est compliqué d’atteindre l’optimum parfait)

Avec ceci en tête, si on veut atteindre l’optimalité totale, il faut trouver le bon compromis entre efficacité du code du site et énergie passée à le concevoir (c’est la vieille histoire du cycle de vie qu’on retrouve dès qu’on parle d’empreinte environnementale).

Anecdote relative au raisonnement : les moteurs électriques dans vos stores ont le rendement le plus pourri qu’on puisse imaginer, mais c’est largement compensé par le fait qu’il ne coûtent pas grand chose (en énergie+matière) à fabriquer.

Cela dit, encore une fois, il ne faut pas voir le clivage "légèreté vs. efficacité du développement" comme une fatalité : il existe vraiment de nos jours des technos qui font sauter cette frontière.

On mentionne Go ici, mais je pense que ça vaut le coup d’expliciter mon point de vue à son sujet. Récemment, en cherchant à changer de boulot, j’ai été amené à développer, pendant un challenge d’embauche, un microservice sous la forme d’une API REST en Go.

Dans mon référentiel, côté backend, j’avais d’ordinaire l’habitude de Django (qui est objectivement une usine à gaz, mais rend le développement tellement aisé !), ou de Flask ou encore de aiohttp (minimalistes et légers — dans le référentiel Python, hein — mais qui rendent en contrepartie les projets assez chiants à bootstrapper), et sur ce coup j’ai découvert buffalo, qui est un framework Go.

  • En termes de confort, j’ai trouvé qu’il n’avait rien à envier à Django : en quelques coups de cuiller à pot, on génère des modèles et des stubs de contrôleurs qu’il suffit de remplir avec la logique métier. Je me suis donc concentré très tôt sur le fonctionnel et la modélisation de mon service, tests et migrations de base de données inclus.
  • Mieux encore, sans atteindre le niveau d’autisme de Rust, j’ai vraiment eu la sensation de gagner un paquet de temps grâce au compilateur de Go, et d’écrire un code qui gérait littéralement tous les cas d’erreur possibles du premier coup,
  • En termes de simplicité, il n’avait rien à envier à Flask : la totalité du code généré est parfaitement intelligible, et même sans avoir jamais utilisé cette techno j’ai pu aller taper dedans sans avoir à chercher le code responsable de tel ou tel comportement par défaut,
  • En termes de performances, Go oblige, il scotche littéralement Python au sol…
  • En termes de légèreté, en utilisant le Dockerfile automatiquement généré par Buffalo, mon application complète tenait dans une image de moins de 50 Mo (je vous laisse comparer ça à l’image python-alpine toute nue…).

En une dizaine d’heures, mon appli était terminée, documentée, testée et l’image Docker livrée, avec en plus une doc swagger en bonus : à aucun moment les outils ne m’ont "chié dans les bottes", et j’ai trouvé le rendement temps de travail vs. performances/qualité du résultat juste époustouflant.

Donc oui, cette expérience a fini de me convaincre du bien-fondé de Go, non seulement "sur le papier", mais également en me montrant que cet écosystème intègre des tas d’outils et de bibliothèques battle-tested pour atteindre un compromis qui éclate en tous points les technos équivalentes les plus populaires : ici un framework web, mais on pourrait aussi citer le couple cobra/viper pour créer des applis en CLI et gérer sa configuration dans n’importe quel format populaire en suivant les conventions standard des 12-factor apps.

Et en ce qui me concerne toujours, cela m’a convaincu que cette techno mériterait que l’on lui fasse beaucoup plus de lumière.

+0 -0

Attention, la criticité du temps de développement dépasse de loin les seuls enjeux économiques. Typiquement, on peut très bien se placer dans le contexte d’un logiciel libre et quand même justifier le besoin d’un langage qui accélère le développement.

Cependant, il ne s’agit plus dans ce cas d’un impératif mais d’une préférence.

C’est une généralité un peu bateau, mais les personnes qui travaillent en sciences humaines ont souvent une approche du temps différente. Nos objets d’étude demandent de lire. C’est assez simple en réalité : juste lire. Mais bien lire demande du temps et de la patience.

En dehors de la réalité économique, il s’agit simplement de prendre le temps. Notre société actuelle n’a plus de culture du temps ; ou plutôt, il faut toujours tout faire très vite.

Comme le dit Bergson :

Si je veux me préparer un verre d’eau sucrée, j’ai beau faire, je dois attendre que le sucre fonde.

Bien entendu, cela ne s’applique pas à une logique d’entreprise.

D’autre part même si l’on rentre dans les considérations écologiques, il faut garder en tête que l’empreinte écologique d’un logiciel dépend en premier lieu de l’échelle à laquelle il est utilisé. Comme je le disais : "on n’est pas google". Sur un site de 2 ans d’âge qui accueille à tout casser 1000 visiteurs réguliers par jour, quelle que soit la technologie employée pour le faire tourner, son empreinte énergétique est de toute façon complètement négligeable devant un dropbox, ou un service comme via navigo, et il en va de même de ses contraintes en performances (si Django ou Rails savent gérer 1000 requêtes par seconde, alors ils suffisent largement). Les problèmes de consommation ne vont donc se poser que du moment où un projet va avoir besoin de passer à l’échelle, or dans la vraie vie, cela ne touche qu’une petite fraction des projets de développement : un sur dix, car les neuf autres projets seront morts avant d’arriver à ce stade, ou bien n’en auront tout simplement jamais besoin.

Sur ce point, je rejoins tout à fait @adri1 : c’est plutôt la question de la multiplication. D’autant plus que l’on peut y ajouter un autre argument : celui du systématisme. Si un principe est bon (consommer moins), pourquoi faudrait-il ne l’appliquer que dans certains cas plutôt que de systématiser son application ?

Par ailleurs, on sait bien comment fonctionne le monde de la programmation : qui programme un outil et le publie est amené à le voir utilisé par d’autres ; qui par définition ne regardent pas vraiment ce qu’il y a à l’intérieur : tant que ça marche !

Par contre, et là-dessus je te rejoins : lorsque l’on en arrive au passage à l’échelle, il convient de remplacer les diverses briques logicielles, en employant cette fois des technologies qui permettent d’aboutir à un code plus léger et (beaucoup) plus performant. Parce qu’une fois que l’on en arrive là, la consommation énergétique du logiciel devient vraiment significative. Et c’est cette étape indispensable qui saute en premier, particulièrement dans l’industrie, le plus souvent pour des raisons économiques, et je le déplore tout autant que toi.

Nous sommes en effet d’accord sur ce point.

Le cas de Rust, que tu cites, est plus délicat. Rust suit le chemin, assez mauvais à mon avis, du C++ moderne. En somme, Rust répond à des problèmes nouveaux en ajoutant des fonctionnalités nouvelles.

Je pense que tu te trompes lourdement sur ce coup : Rust répond à des problèmes qui ont plus de cinquante ans d’existence, mais en utilisant des notions théoriques modernes. Quand on a un peu de recul et d’expérience dans le développement, on se rend bien vite compte que si Rust est complexe, c’est parce qu’il rend apparente la complexité intrinsèque des problèmes auxquels tous les programmes finissent par être confrontés un jour ou l’autre. En d’autres termes : le compilateur de Rust est une machine à voyager dans le temps, dans le sens où il t’impose de gérer dès la compilation des problèmes auquel le code équivalent en C finirait tôt ou tard par rencontrer en production, sous la forme d’un segfault ou d’un comportement indéterminé (très douloureux à déboguer).

Néanmoins, ce n’est pas non plus une panacée universelle : il est parfaitement adapté pour créer des briques logicielles qui sont critiques prises individuellement, mais il serait tout aussi déraisonnable de généraliser cette approche de la safety à la totalité d’une application, qu’il le serait de porter des gants et lunettes de protection ainsi qu’une armure en kevlar pour faire cuire des pâtes. :)

nohar

Je pense que je me suis mal exprimé.

Il est évident que les problèmes d’aujourd’hui ne sont pas essentiellement différents ; mais ils se posent dans un cadre différent : celui d’un développement qui doit être plus rapide et qui masque l’algorithmique, l’informatique "pure" derrière l’abstraction.

Prenons maintenant une analogie. Une bonne explication, c’est une explication qui expose simplement un phénomène complexe. Je crois qu’il en est de même pour la programmation. Un bon programme, c’est celui qui résout avec des éléments simples un problème compliqué.

Je me plaignais, dans le précédent sujet, des habitudes jargonnantes des études littéraires. La complexité de Rust est, pour moi, proche du jargon. Go, puisqu’il est d’usage de comparer ces deux langages, peut résoudre les mêmes problèmes : mais en utilisant des éléments simples.

J’ai très longtemps côtoyé les segfault et autres petites gourmandises : j’ai programmé des extensions Ruby en C. Une horreur ; mais qui provient autant de l’implémentation traditionnelle nébuleuse et objectivement vieillissante de Ruby que du C en lui-même. Cependant, cela veut simplement dire que la solution trouvée n’est pas exactement la bonne. Il y a une certaine beauté, je crois, à trouver la bonne solution avec le plus petit nombre d’éléments nécessaires.

Je me permets de rajouter ceci : dans le cas de ZdS, il ne me semble pas impossible que le temps de développement et de test représente une partie non-négligeable de la consommation d’énergie du site (les gentils bénévoles qui font ça sont des ordis avec des écrans qui consomment a priori bien plus qu’un serveur convenable).

luxera

Je trouve cette réflexion très intéressante.

Je suis d’ailleurs, outre l’écran, toujours effaré par la quantité de services en arrière-plan dont je n’ai que faire, ou pour lesquels je serais volontiers prêt à attendre une poignée de seconde.

Je me trouve à travailler sur ordinateur portable par la force des choses, et je suis désespéré par la fréquence à laquelle je dois recharger la batterie tandis que je ne travaille que dans un terminal avec un Vim quasiment sans plugins, et éventuellement Musikcube ainsi qu’un navigateur web.

Pour se rendre compte, ce serait intéressant d’avoir un moyen d’obtenir une estimation temps réel de la consommation électrique de son équipement. J’ignore si de tels outils existent.

Et en ce qui me concerne toujours, cela m’a convaincu que cette techno mériterait que l’on lui fasse beaucoup plus de lumière.

nohar

Tout à fait. Cependant, je crois que Go souffre de son apparente austérité.

D’une part, les programmeurs cherchent en premier lieu une approche orientée objet : il n’y a qu’à voir comment javascript se tord le cou pour essayer de mimer la programmation orientée objet depuis des années.

Mais cela a aussi à voir, je pense, avec la nécessité que nous évoquions : le nombre réduit de concepts fait peur. Il n’y a qu’à voir tous ces sujets qui demandent où sont les map, reduce, génériques, etc. quand quelques for bien ajustés, augmentés de quelques conditions çà et là, font l’affaire dans la plupart des cas.

Si un principe est bon (consommer moins), pourquoi faudrait-il ne l’appliquer que dans certains cas plutôt que de systématiser son application ?

Je ne dis pas qu’il ne faudrait pas le faire (et en ce qui me concerne c’est un point qui me soucie également au travail depuis quelques temps), mais simplement j’essaye de dégager les problématiques très concrètes qui expliquent que tout le monde ne le fasse pas.

Je pense que personne ne peut contredire le fait qu’il soit absolument vital pour notre avenir de rendre les technologies légères et efficaces à nouveau, et d’arrêter de chauffer les data-centers pour rien. Simplement, je suis également convaincu que pour que cela fonctionne, alors il faut que ces solutions et ces approches "vertes" soient au moins aussi aisées à mettre en œuvre que les canons de l’industrie : les deux besoins sont légitimes, ils se conjuguent et leurs solutions ne sont pas mutuellement exclusives.

+2 -0

Je trouve que c’est un sujet très intéressant. Quelques remarques.

Interprété contre compilé ?

En formulant un sous-débat sous l’angle "interprété contre compilé", et avec des arguments de "temps de programmation contre temps d’exécution" ce fil donne l’impression que les langages dynamiques interprétés rendent les systèmes plus rapides à développer et plus lents à s’exécuter. Les deux idées sont à nuancer.

D’une part, un langage interprété ne rend pas forcément un système plus lent, tant qu’il est utilisé dans des domaines non critiques pour les performances (où la rapidité du langage n’est pas le facteur determinant pour les performances). Un exemple parmis d’autres est Mercurial, un système de gestion de version majoritairement écrit en Python, comparé à Git, un système de gestion de version majoritairement écrit en C (en fait, en Bash et C; mais le noyau C de mercurial est beaucoup plus petit que la couche C de git). Mercurial n’est pas plus lent que Git, car ce sont les opérations disque qui dominent les performances.

Ensuite, je ne suis pas convaincu qu’un langage dynamique permette de réduire le temps de développement, toutes choses (écosystème logiciel, expérience, etc.) étant égales par ailleurs. On compare souvent les langages dynamiques les plus connus (Python, Ruby, Perl en son temps) aux langages statiques les plus connus (C/C++, Java); ces derniers sont très verbeux et rendent la programmation pénible sans un outillage important; mais il existe des langages statiques qui font beaucoup plus d’inférence de type, et où on écrit finalement des programmes avec un degré de concision comparable à un langage dynamique. Et dans tous les cas, la verbosité du langage peut être déterminante pour le temps de sortie d’un premier prototype, mais la plupart des logiciels sont maintenus et améliorés longtemps après ce premier prototype, et là les langages typés dynamiquement ont un désavantage important: en terme de maintenance, compréhension du code, refactoring, etc., l’absence de types statiques ralentit beaucoup le développement. D’ailleurs la plupart des gros utilisateurs industriels de langages dynamiques ont investi des millions, ces dernières années, pour développer des systèmes de typage statiques par-dessus leurs langages (Mypy pour Python, Flow ou Typescript pour Javascript, Hack pour PHP, Ruby s’y met, etc.); cela confirme qu’il semble y avoir un besoin important de typage pour des projets logiciels plus conséquents que des prototypes.

Pas que les performances

Je ne suis pas d’accord avec sa comparaison entre Go et Rust, mais Hélio a raison de parler de complexité (et donc de simplicité) dans son message. Il n’y a pas que les performances qui sont importantes pour produire du logiciel plus respectueux de la planète et des humains. Quelques critères :

  • Les langages, les programmes doivent être les plus simples possibles, si on veut pouvoir recevoir des contributions du plus grand nombre, espérer que les utilisateurs s’emparent de leur logiciel. La simplicité n’est pas un état primitif, naturel, c’est le résultat d’un énorme travail — il est toujours plus facile de faire compliqué.

    Mais rogner sur des fonctionnalités fondamentales, comme Go qui manque de généricité, ne donne souvent qu’une apparence de simplicité: on déporte vers les utilisateurs ou vers les outils les difficultés qu’on n’a pas résolues au niveau du langage. Ignorer la complexité du domaine introduit de la complexité accidentelle.

  • Partager ses outils et ses connaissances permet d’économiser ses ressources. Le logiciel libre permet à des personnes isolées de s’unir pour développer une solution commune, au lieu de forcer chacun à choisir entre payer et refaire, dupliquant du travail. Pour que ce partage soit réel et non seulement possible, il faut quand même investir des efforts dans le fait d’ouvrir son développement, partager la connaissance, communiquer.

  • Une solution bien conçue peut être plus durable sur le long terme, même si elle est moins populaire à un instant donné, moins plaisante à utiliser, etc.

  • Certains programmes (libres ou non) nous rendent dépendants de leur fonctionnement, au lieu de nous permettre de faire plus de choses. Par exemple, un logiciel de création de documents sans fonction d’export permettant le ré-emploi dans un autre logiciel nous enferme; pareil pour un site de publication de contenus. Un logiciel de communication sans préservation de historique dans un format utilisable. Un service en ligne qui centralise et enferme notre travail.

Pas que l’économie

La question même de "la nécessité", de l’usage raisonné des ressources, d’une approche austère ou conviviale de l’informatique nous place en dehors du champ seul de l’économie; c’est une question qui contient une part de radicalité.

On peut la discuter avec une perspective écologique, mais aussi sociale (quel rôle joue le logiciel dans le façonnement de notre société, et quel type de logiciel pourrait jouer un rôle meilleur ?), politique, etc. Ces perspectives ne remplacent pas le point de vue économique, mais elles le complémentent, aussi valides et souvent aussi importantes.

Il y a des développements majeurs en informatique qu’on ne peut pas comprendre seulement à travers une perspective économique. Par exemple, le mouvement du logiciel libre n’était pas motivé par des raisons économiques et son développement repose sur des logiques idéologiques et sociales — même si des facteurs économiques ont évidemment aussi joué dans son adoption large par l’industrie informatique. Les questions dont on parle dans ce fil sont d’un ordre aussi fondamental (et aussi idéologique et social) que la question du logiciel libre, je pense, bien que moins conceptualisées.

À plus petite échelle, si on prend quelques langages cités dans ce fil, on voit que les aspects économiques jouent étonamment peu, ou indirectement, pour déterminer le développement et l’adoption d’une technologique. Go ne vient pas d’un besoin métier clair de Google, mais du fait que l’entreprise a embauché les créateurs de Plan9 (avant tout pour des raisons de publicité, ça faisait bien dans la liste des employés) et qu’ils ont pu refaire la même chose avec des ressources internes. Rust n’est pas arrivé par calcul économique de Mozilla, mais grâce à un créateur (Graydon Hoare) érudit, passionné de l’étude des langages de programmation unversitaires, et grâce à une vision long terme d’une partie de Mozilla, placée hors de la recherche du profit, qui a su protéger le projet et le faire grandir.

+4 -0

Il y a beaucoup de choses intéressantes dans ce message. J’aimerais compléter seulement quelques points.

En formulant un sous-débat sous l’angle "interprété contre compilé", et avec des arguments de "temps de programmation contre temps d’exécution" ce fil donne l’impression que les langages dynamiques interprétés rendent les systèmes plus rapides à développer et plus lents à s’exécuter. Les deux idées sont à nuancer.

Il faudrait ajouter, outre cela, un dernier élément : la nécessité d’un retour à la matière informatique.

Le principe d’un langage interprété est de faire oublier la machine. Bien entendu, tout langage a la prétention de favoriser, de faciliter l’accès aux fonctions de la machine : personne ne veut développer en assembleur, encore moins en binaire. Cependant, le langage interprété a la particularité de substituer à la machine une idée de celle-ci. Il y a, à mon avis, quelque chose à creuser autour de la conscience de la machine chez le programmeur.

Bien entendu, les programmeurs qui travaillent sur des systèmes embarqués sont beaucoup plus proches de ces questions ; j’ai toujours eu beaucoup d’admiration pour ce travail et je crois qu’il serait, pour compléter tout ce que tu énumères fort justement, nécessaire de renouveler le lien à la machine dans l’approche de la programmation.

  • Les langages, les programmes doivent être les plus simples possibles, si on veut pouvoir recevoir des contributions du plus grand nombre, espérer que les utilisateurs s’emparent de leur logiciel. La simplicité n’est pas un état primitif, naturel, c’est le résultat d’un énorme travail — il est toujours plus facile de faire compliqué.

J’ajouterais, de manière presque hors-sujet, qu’il y a dans la simplicité un idéal esthétique. Il ne faut pas oublier que la programmation reste la mise en usage d’un langage. Cette idée n’est souvent pas tout à fait exploitée à mon avis.

Si l’on parle couramment de code idiomatique, on oublie généralement ce que cela suppose : le code peut répondre plus ou moins à une norme, souvent en partie tacite. Dès lors, il se pose nécessairement la question d’un rapport poétique au langage de programmation, au sens premier, grec. La programmation est un travail, une technique, le résultat d’un temps dévolu à un tâche et qui, de fait, tend vers un idéal, et cet idéal est forcément esthétique d’une manière ou d’une autre.

Cela me fait penser qu’un article sur le code comme potentielle forme poétique serait très intéressant. Il reste à voir si je trouve la motivation nécessaire à sa rédaction.

Mais rogner sur des fonctionnalités fondamentales, comme Go qui manque de généricité, ne donne souvent qu’une apparence de simplicité: on déporte vers les utilisateurs ou vers les outils les difficultés qu’on n’a pas résolues au niveau du langage. Ignorer la complexité du domaine introduit de la complexité accidentelle.

Je suis en partie d’accord avec cela.

Il est évident que certains problèmes ne sont pas résolus par le langage. Mais doivent-ils l’être ? La complexité est inhérente aux problèmes posés : elle est là, inexorable. Il ne s’agit pas de la combattre pour elle-même mais de savoir quoi en faire.

La simplicité d’une solution, il me semble, est indissociable de la simplicité des éléments par laquelle elle s’obtient. C’est aussi ce que recouvre le mot « simple » : quelque chose qui n’est pas « composé ».

En ce sens, cette remarque, si elle est intéressante, n’est pas pertinente jusqu’au bout, car elle ne pointe du doigt qu’une seule face de la simplicité, qui n’est aussi pas essentiellement simplicité : elle est facilité. Si la simplicité comprend la facilité, elle ne saurait se réduire à elle.

Pour nuancer cependant ce propos, il faut noter que Go étudie de très près (et je crois même que cela est peut-être déjà décidé) l’ajout de généricité. C’est en effet sans doute une fonctionnalité essentielle qui fait actuellement défaut à Go.

Go ne vient pas d’un besoin métier clair de Google, mais du fait que l’entreprise a embauché les créateurs de Plan9 (avant tout pour des raisons de publicité, ça faisait bien dans la liste des employés) et qu’ils ont pu refaire la même chose avec des ressources internes.

Je pense qu’il faut compléter cette information : car Google a aussi trouvé dans Go le moyen d’alléger sa charge serveur et de faciliter la concurrence. Il y a sans doute à la fois un aspect publicitaire et un aspect nécessaire au développement de Go.

Rust n’est pas arrivé par calcul économique de Mozilla, mais grâce à un créateur (Graydon Hoare) érudit, passionné de l’étude des langages de programmation unversitaires, et grâce à une vision long terme d’une partie de Mozilla, placée hors de la recherche du profit, qui a su protéger le projet et le faire grandir.

Je n’arrive pas à avoir confiance à ce point en Mozilla pour croire que leurs perspectives sont tout à fait dénudées d’intérêt. C’est peut-être là une préoccupation idiote, mais la trahison de Canonical a laissé des traces. Je ne suis plus prêt à accorder autant de crédit à la structure gargantuesque que devient peu à peu Mozilla.

Ensuite, je ne suis pas convaincu qu’un langage dynamique permette de réduire le temps de développement, toutes choses (écosystème logiciel, expérience, etc.) étant égales par ailleurs

Je pense qu’on est d’accord, mon discours de base justement sur le fait que toutes choses ne sont pas égales par ailleurs.

Cet exemple est peut-être biaisé, mais si je souhaite réaliser une application asynchrone, il m’est beaucoup plus facile d’aborder mon projet avec asyncio en Python qu’avec tokio en Rust : la courbe d’apprentissage n’est pas du tout la même, et il est globalement plus facile de former des développeurs au premier qu’au second.

+0 -0

Merci pour vos partages et opinions, très intéressants à lire. Énormément de réflexions intéressantes, trop longues à toutes relever. Merci aux intervenants.

Y’a un petit détail qui m’a fait tiquer plusieurs fois Helios, si tu me permets

Le principe d’un langage interprété est de faire oublier la machine.

J’ai l’impression que tu mêles plusieurs fois "langage interprété" et "langage haut-niveau".

Une partie de ce que tu exprimes à propos des performances n’est pas nécessairement lié à interprété / compilé.

Prenons l’exemple du JIT (que tu citais) (qui pourrait d’ailleurs fonctionner avec le Garbage collector, d’ailleurs, qui existe en Go, si je ne m’abuse).

Il existe des langages compilés qui tirent partir d’un JIT. Prenons l’exemple de la JVM (histoire que je sois relativement à l’aise pour expliquer ma pensée) : il pourrait apparaître logique de se dire "c’est une perte pure de ressources que de faire tourner un programme qui va chercher à optimiser mon programme".

Sauf que dans les faits, les optimisations effectuées par le JIT de la JVM lorsque mon programme s’exécute sont très largement au-delà de celles auxquelles je pourrais moi-même aboutir en écrivant du bytecode, ou pire : en écrivant le code assembleur qui est optimisé in fine.

Il y a donc quasiment 100% de chances que mon programme, écrit en Java/Scala/Kotlin/Clojure/Ruby(Jruby ayant parfois de bien meilleures perfs que l’interpréteur Ruby), soit bien mieux optimisé lorsqu’il tourne dans une JVM que la version ASM que j’aurais pu écrire. A l’échelle cela représente un gain gigantesque.

Ensuite, si on transfère le débat "interprété vs. compilé" au domaine "proche de la machine vs. idiomatique" et la notion de simplicité / complexité que vous êtes plusieurs à aborder dans vos différents messages. Là encore j’ai un avis mitigé.

Comme évoqué : "a fonctionnalités égales" bien entendu il sera préférable d’utiliser la version la moins gourmante en ressources, la mieux optimisée. Est-ce-que cela signifie rigoureusement de choisir un langage proche de la machine comme Go ou Rust ? Je suis loin d’en être convaincu cf. mon point sur le JIT ci-dessus.

Mais quand bien même ? On dit "à fonctionnalités égales", soit, je dirais "à risque égaux". Et là, il peut y avoir un monde qui n’apparaît pas beaucoup dans vos échanges.

Admettons que je sois en train de développer un système d’optimisation de chaîne logistique. Trivialisons en disant que ce système doit gérer des conteneurs, des bateaux, avions, poids-lourds, des usines de production et d’assemblage, plus ou moins proches du lieu où le produit est consommé (j’entends par là : où il est utile).

Tous ces systèmes (la chaîne de production de l’usine, la chaîne de montage, les transports, etc.) communiquent entre eux, donc les composants informatiques assurant le transport de la donnée rentrent dans les 10% exposés par nohar : ils doivent passer à l’échelle, et être optimisés pour économiser des ressources.

On a parlé du coût humain d’entretien logiciel, parlons du coût d’un défaut logiciel maintenant, et pas en termes humains, mais bien sur le volet écologique.

Dans ce système, le moindre bug introduit par un choix technique :

  • comportement inattendu en C
  • erreur de modélisation de mon problème car le manque d’abstraction (système de type, par ex.) a rendu cette modélisation extrêmement difficile
  • problème de conversions d’unité de mesure ou de devises
  • pas d’abstraction autour des calculs flottants, ou problèmes liés à des débordements etc.

Tous ces problèmes qui pourraient apparaître avec un langage pourtant optimal au niveau du "runtime" ont potentiellement un impact écologique désastreux (armement d’un porte-conteneur complètement vide, choix d’une usine de production en Chine pour une consommation en Europe alors qu’un producteur local faisait exactement la même chose, mais j’ai mal positionné la virgule au moment de convertir de miles en mètres, ou ai subi un débordement, que sais-je).

Que dire du système de types, aussi ? Ai-je envie de représenter le problème que je cherche à résoudre, automatiser, optimiser dans un langage proche des mathématiques ou proche de la machine (donc de l’électronique, quasiment).

Par exemple : est-ce-que ça ne vaudrait pas le coup d’implémenter mon bidule en Haskell, ou OCaml ? Pour bénéficier d’un système de type puissant qui rend mon problème plus clair. Ou "let it crash" avec Erlang/Elixir ? Est-ce-que BEAM atteindra le niveau de performances de la version que j’aurais pu écrire en C ? Est-ce-que Go m’aurait vraiment permis de modéliser tous les cas aux limites qui peuvent se produire, ou aurait-il panic à la première occasion ?

Évidemment, on est loin du pur constat, avec lequel je suis bien entendu tout à fait d’accord, que charger 12M de scripts pour afficher un élément d’expérience utilisateur sur une page web est un véritable drame. Mais je pense qu’il faut quand même éviter de trop simplifier le problème et bien prendre en compte que s’il existe autant de solutions (langages, sgbd, etc.) c’est aussi affaire de compromis, malheureusement.

On ne travaille (ou se passionne) pas dans un monde dans lequel on aurait d’un côté une solution optimales pour tous les besoins, et de l’autre le reste des solutions sous-optimales, et à bannir. Ca serait tellement plus simple, cela dit.


EDIT parce qu’il manque une certaine forme de conclusion : On peut avoir tendance à penser que la sous-optimisation en ressources de nos "produits" (~= résultat d’un travail) logiciels sont la conséquence d’une forme de feinéantise. C’est sans doute sûrement vrai et il est important de ne pas se le cacher. Mais il ne faut pas avoir tendance à trop simplifier et s’auto-flageller gratuitement non plus. Parfois c’est affaire de compromis, malheureusement.

+1 -0

Go ne vient pas d’un besoin métier clair de Google

Selon une source sur laquelle je n’arrive plus à remettre la main, Go a quand même été développé pour satisfaire de grosses contraintes de coût sur leur chaîne CI/CD : outre les critères classiques que l’on lui connaît (simplicité et promotion de l’approche CSP pour la concurrence), l’un des besoins auxquels aucune technologie existante n’apportait de réponse satisfaisante au moment de son design, était d’obtenir des builds reproductibles et économes en ressources, notamment via une gestion explicite des dépendances transitives lors de la compilation.

Il est d’ailleurs assez amusant de noter que la réponse de Go à ce problème a pendant bien longtemps été suffisante pour Google mais un peu faible dans le contexte de l’open-source, car la totalité de la codebase des divers produits Google est maintenue dans un dépôt monolithique et gigantesque, ce qui facilite grandement la gestion du cycle de vie des divers composants logiciels, alors que l’Open Source s’organise par nature autour de composants (et donc de dépôts de code) indépendants les un des autres. Dans ces conditions, il a fallu attendre l’introduction récente des Go modules (qui miment un petit peu le `Cargo.toml) pour jouir d’une gestion des dépendances suffisamment claire dans un contexte multi-dépots.

J’aimerais aussi tempérer ce propos :

Mais rogner sur des fonctionnalités fondamentales, comme Go qui manque de généricité, ne donne souvent qu’une apparence de simplicité: on déporte vers les utilisateurs ou vers les outils les difficultés qu’on n’a pas résolues au niveau du langage. Ignorer la complexité du domaine introduit de la complexité accidentelle.

Ici, tu sembles considérer comme acquis que la généricité est une fonctionnalité "fondamentale", pourtant, cela va bientôt faire 10 ans que je travaille dans des langages qui n’en disposent pas, et dans lesquels son absence ne m’a jamais manqué, contrairement à des choses moins sexy et plus terre à terre, comme un compilateur capable de relever, sans outillage supplémentaire, le non-respect d’une interface (en Python, cela demande d’abuser du module abc), ou une erreur possible non rattrapée, ou bien rattrapée puis implicitement ignorée (ne pas uriliser la variable err après l’avoir affectée suite à l’appel d’une fonction).

À mes yeux, généricité n’est pas du tout un mécanisme indispensable (même si on pourrait se persuader du contraire en se bornant à quelques exemples théoriques simples), et son absence appelle simplement d’autres façons de concevoir un programme ou des abstractions : si ces abstractions sont moins puissantes/fiables, je ne demande qu’à en être convaincu, mais l’expérience me suggère qu’en fait, c’est kif-kif.

Je ne trouve pas, typiquement, que le code de docker soit illisible ou difficile à suivre. :)

Edit: Le blog de Go semble aborder le sujet ici : https://blog.golang.org/why-generics mais une fois encore, il se base sur un exemple de code simpliste que je ne trouve pas spécialement convaincant.

+1 -0

@Javier: je suis globalement d’accord, et je pense que tu as drôlement raison d’insister sur l’importance de la correction du code, en plus des performances, et des coûts des failles informatiques. Pour moi le fait d’avoir un code correct est primordial. D’ailleurs je soupçonne que nos systèmes actuels "rapides" passent en fait une partie de leur temps à se protéger de leur propre fragilité, et qu’une approche où nous avons plus confiance (meilleure conception, et des outils qui facilitent le raisonnement) pourraient au final être plus efficace.

(Une nuance au niveau du JIT: dans mon expérience, pour la plupart des langages qui ont investi des ressources dans une approche JIT, c’est pour pallier à des erreurs de conception en amont. C’est évident dans le cas de Javascript qui est une erreur-de-conception-sur-pattes, mais même dans le cas de Java les JIT ont d’abord été introduits pour optimiser le test dynamique de typage demandé par la covariance des tableaux mutables. Dans des langages mieux conçus, des approches plus simples (inline caches, profile-guided optimization) permettent d’obtenir les mêmes bénéfices en performance.)

La notion de l’importance de la correction est elle-aussi motivée par des raisons non-économiques. Ça ne coûte pas cher aujourd’hui d’écrire des programmes buggés, parce que la société fait porter le coût de ces bugs sur les utilisateurs des programmes, pas leurs auteurs. J’espère que notre société va évoluer sur ce point, mais en attendant là où l’argent ne parle pas je pense que nous, programmeurs, devrions mettre en place un cadre éthique fort pour justifier d’investir des efforts dans la correction du code. (En commençant par éviter les technologiues qui encouragent les bugs, comme C par exemple.)

J’ai parlé de radicalité dans mon message. Une forme de radicalité intéressante (je l’ai entendue d’Adam Chlipala, un professeur/chercheur d’informatique aux états-unis) est de dire : je ne veux pas construire mon système au-dessus d’un sous-système mal conçu. J’aime beaucoup cette façon de penser qui est exigeante et fructueuse, même si évidemment il n’est pas réaliste de l’appliquer partout, tout le temps. (Je n’aime pas x86 mais je n’ai pas trouvé d’ordinateur RISC-V qui me convienne pour l’instant. Je pense que Linux est mal conçu, mais les systèmes beaucoup mieux conçus sont aussi difficilement utilisables comme OS personnels pour l’instant.)

@nohar: je trouve qu’on s’éloigne un peu du sujet de départ en rentrant dans les détails de Go. Est-ce que tu serais partant pour ouvrir un nouveau sujet sur la question ? Pour te donner envie (peut-être ?) je peux citer un autre défaut de Go: l’absence de types sommes, qui encore une fois empêche de modéliser proprement dans le code des aspects naturels d’un problème. On pourrait essayer de regarder le code de docker pour trouver des endroits qui seraient améliorables si ces fonctionnalités existaient; mais, encore une fois, dans un sujet à part.

+1 -0

pourtant, cela va bientôt faire 10 ans que je travaille dans des langages qui n’en disposent pas, et dans lesquels son absence ne m’a jamais manqué

On peut à peu près tout justifier avec cet argument, cela dit :P

Après tout, on a fait décoller des fusées avec du code écrit en assembleur :)

J’ai fini par alimenter des docs de "problèmes que j’ai résolus différemment" dans certains langages avant de les résoudre autrement dans d’autres.

C’est tout à fait possible, parfois c’est mieux parfois moins bien.

Je crois que l’exemple qui revient le plus souvent c’est "strategy/adapter" que j’ai implémenté 50 fois en OOP, et que j’ai redécouvert à travers les type classes dans d’autres langages. Là c’est purement une question de "surcharge mentale". En Haskell, Rust : c’est vraiment plus simple et plus puissant, découplé. En Scala c’est plus puissant mais pas nécessairement plus simple (parce que la résolution des implicit est assez galère). Dans ce cas, on pourrait presque simplement parler de "confort" (sauf que c’est quand même un peu plus puissant), soit.

Par contre pour les unions/intersections de types, le pattern matching, ou les higher-kinded types : bien entendu qu’on peut écrire du code sans, mais :

  • soit tu perds vraiment (et lourdement) en "sécurité" (exhaustivité du pattern matching par exemple)
  • soit tu te retrouves à écrire beaucoup de code (donc beaucoup de bugs potentiels) pour une fonctionnalité pourtant simple,
  • soit, carrément (et c’est vraiment souvent le cas pour les HKT) tu vas perdre soit en sécurité ("cast" au runtime) soit en performance (if ... instanceOf ... coûteux par exemple en Java, ou pire, introspection de types au runtime).

C’est pas si marginal qu’on pourrait le croire, y’a énormément de bibliothèques qui utilisent l’introspection (ou la méta-programmation) pour résoudre des soucis qui n’existeraient pas avec un meilleur système de types, l’air de rien, ça introduit un sacré coût au runtime !

On pourrait essayer de regarder le code de docker pour trouver des endroits qui seraient améliorables si ces fonctionnalités existaient

Super exercice ! Je connais pas Go mais ça m’intéresse à fond :)

EDIT : pure coïncidence, je viens de tomber là dessus sur Twitter. L’exemple n’est pas totalement parfait, on aurait pu, en Java, s’en sortir quand même (une enum qui implémente une interface) m’enfin on aurait perdu beaucoup dans le process, et ça a le mérite de raisonner sur un exemple simple.

EDIT2 : on pourrait parler des types opaques aussi. C’est un truc qui paraît complètement nul et inintéressant, on peut bien évidemment faire sans, pourtant travailler dans un éco-système où c’est la norme (pas utiliser des string mais des types opaques quasi-systématiquement) réduit une quantité considérable de bugs, et rend le "checking" obligatoire).

+0 -0

La notion de l’importance de la correction est elle-aussi motivée par des raisons non-économiques. Ça ne coûte pas cher aujourd’hui d’écrire des programmes buggés, parce que la société fait porter le coût de ces bugs sur les utilisateurs des programmes, pas leurs auteurs. J’espère que notre société va évoluer sur ce point, mais en attendant là où l’argent ne parle pas je pense que nous, programmeurs, devrions mettre en place un cadre éthique fort pour justifier d’investir des efforts dans la correction du code. (En commençant par éviter les technologiues qui encouragent les bugs, comme C par exemple.)

L’argument est intéressant sur le fond, mais il n’est pas très convaincant. C’est même l’exemple que tu cites qui le fait s’effondrer.

Tu évoques la nécessité d’avoir un code correct. Je suis d’accord. La rigueur est une conséquence obvie de tout ce qui a été dit très justement dans les précédents messages : l’exigence de simplicité, d’efficacité, le partage nécessaire du code, entre autres.

Tu cites un peu plus tôt dans ton message les stratégies des langages qui ont pour but de corriger des défauts de conception. Je suis tout à fait d’accord avec ce constat.

Dès lors, comment peux-tu pointer le C du doigt ? Si nous ne devions retenir qu’un langage pour apprendre la rigueur du code, l’importance d’un code correct et irréprochable, ce serait probablement le C. Ton argument se retourne contre ton propre discours : si les programmes en C sont davantage sujets à des bogues, ce n’est pas le fait du langage et de ses stratégies, c’est le fait du manque de correction du code.

Par contre pour les unions/intersections de types, le pattern matching, ou les higher-kinded types : bien entendu qu’on peut écrire du code sans, mais :

  • soit tu perds vraiment (et lourdement) en "sécurité" (exhaustivité du pattern matching par exemple)
  • soit tu te retrouves à écrire beaucoup de code (donc beaucoup de bugs potentiels) pour une fonctionnalité pourtant simple,
  • soit, carrément (et c’est vraiment souvent le cas pour les HKT) tu vas perdre soit en sécurité ("cast" au runtime) soit en performance (if ... instanceOf ... coûteux par exemple en Java, ou pire, introspection de types au runtime).

Seulement, tout ceci ne va pas forcément dans le sens de la solution minimale.

La question de la sécurité telle que tu la définis est une fausse question. Il appartient au programmeur d’envisager tous les cas possibles et d’en construire une représentation. C’est même le cœur de la programmation, cœur qu’elle partage avec les mathématiques. Une démonstration mathématique qui oublie de traiter des cas particuliers qui la mettent en péril est une démonstration incomplète, sinon fausse. Il en va sans doute de même d’un programme.

Pour l’argument de la quantité de code, ce n’est pas forcément vrai. Il a déjà été fait plusieurs fois mention sur ce fil de l’importance du partage du code, même si ce partage ne doit pas être aveugle comme il a tendance à l’être de plus en plus, notamment autour des technologies web. En somme, la quantité de code à la charge du programmeur n’est pas nécessairement amenée à varier ; et les bogues sont pour l’essentiel une question de rigueur.

La question de la sécurité telle que tu la définis est une fausse question. Il appartient au programmeur d’envisager tous les cas possibles et d’en construire une représentation. C’est même le cœur de la programmation, cœur qu’elle partage avec les mathématiques

C’est très exactement pour cette raison qu’il doit l’exprimer dans un langage plus proche des mathématiques que de l’électronique.

+0 -0

La question de la sécurité telle que tu la définis est une fausse question. Il appartient au programmeur d’envisager tous les cas possibles et d’en construire une représentation. C’est même le cœur de la programmation, cœur qu’elle partage avec les mathématiques

C’est très exactement pour cette raison qu’il doit l’exprimer dans un langage plus proche des mathématiques que de l’électronique.

Javier

Il ne me semble pas satisfaisant de considérer, si proches et utiles l’une à l’autre que soient ces deux disciplines, les représentations de l’informatique comme celles des mathématiques. La question du rapport à la machine et à la matière a été évoquée. Je crois que c’est un point central et qu’il ne faut pas mésestimer ses conséquences.

pourtant, cela va bientôt faire 10 ans que je travaille dans des langages qui n’en disposent pas, et dans lesquels son absence ne m’a jamais manqué

On peut à peu près tout justifier avec cet argument, cela dit :P

Après tout, on a fait décoller des fusées avec du code écrit en assembleur :)

J’ai fini par alimenter des docs de "problèmes que j’ai résolus différemment" dans certains langages avant de les résoudre autrement dans d’autres.

C’est tout à fait possible, parfois c’est mieux parfois moins bien.

Je crois que l’exemple qui revient le plus souvent c’est "strategy/adapter" que j’ai implémenté 50 fois en OOP, et que j’ai redécouvert à travers les type classes dans d’autres langages. Là c’est purement une question de "surcharge mentale". En Haskell, Rust : c’est vraiment plus simple et plus puissant, découplé. En Scala c’est plus puissant mais pas nécessairement plus simple (parce que la résolution des implicit est assez galère). Dans ce cas, on pourrait presque simplement parler de "confort" (sauf que c’est quand même un peu plus puissant), soit.

Par contre pour les unions/intersections de types, le pattern matching, ou les higher-kinded types : bien entendu qu’on peut écrire du code sans, mais :

  • soit tu perds vraiment (et lourdement) en "sécurité" (exhaustivité du pattern matching par exemple)
  • soit tu te retrouves à écrire beaucoup de code (donc beaucoup de bugs potentiels) pour une fonctionnalité pourtant simple,
  • soit, carrément (et c’est vraiment souvent le cas pour les HKT) tu vas perdre soit en sécurité ("cast" au runtime) soit en performance (if ... instanceOf ... coûteux par exemple en Java, ou pire, introspection de types au runtime).

En toute honnêteté, je vois bien un cas d’utilisation où les types algébriques m’ont réellement manqué, c’était le jour où on a écrit un compilateur pour un DSL en Python : dans ce genre de cas, effectivement, on est bien plus confort à utiliser un langage qui implémente ce genre de types (les enum de Rust) ainsi que du pattern matching. Du moins dans ma façon de concevoir le code d’un parseur ou d’un visiteur, je me sentirais bien plus à l’aise avec ce mécanisme. Sauf que je n’implémente pas des compilateurs tous les jours, et ça aussi c’est à prendre en considération : j’ai l’air de prêcher pour Go sur ce thread, mais c’est surtout parce que Go s’adapte particulièrement bien au genre de programmes que j’écris et maintiens tous les jours. Dans d’autres cas plus marginaux de mon métier, notamment pour écrire une bibliothèque qui devait manipuler des fichiers binaires contenant des données typées, je lui préfère, sans discuter, le typage, la gestion des durées de vies, les zero cost abstractions et surtout les macros de Rust (ainsi que nom <3).

C’est plutôt ça que je voulais exprimer par "je n’en ai jamais ressenti le manque en 10 ans" : je ne crois pas que mon métier m’ait amené à réellement toucher des sujets où la généricité ou les types algébriques sont indispensables, dans le sens où 99% du temps, je pense pouvoir me passer de ce type de polymorphisme : à part dans un compilateur (cas extrême) où le pattern matching nous change la vie, je ne crois pas avoir si souvent que ça croisé de cas où l’on pouvait simplement se contenter d’un switch (ou un dispatcher en Python), sans impact réellement significatif sur les perfs : certes, on perd un filet de sécurité en écrivant le module correspondant, mais je ne crois pas que ce soit significatif à l’échelle du projet.

L’autre point sur lequel j’aimerais insister est abordé sur le billet de blog que je cite plus haut : introduire la généricité ou des types algébriques dans un langage revient à le rendre un peu plus complexe, et l’approche des gars de Go est plutôt intéressante à ce sujet, puisque ça fait littéralement 10 ans qu’ils en parlent et itèrent sur la question, pour ne l’introduire effectivement que le jour où ils auront trouvé un moyen de l’introduire avec un impact acceptable sur la complexité du langage. Pour moi, c’est le paroxysme de l’approche KISS.

On pourrait essayer de regarder le code de docker pour trouver des endroits qui seraient améliorables si ces fonctionnalités existaient

Super exercice ! Je connais pas Go mais ça m’intéresse à fond :)

Javier

Ça ferait un super sujet en effet. Je ne vais pas avoir le temps de le créer aujourd’hui, mais je crois que l’expérience pourrait être extrêmement enrichissante à plus d’un titre.

+0 -0

Dès lors, comment peux-tu pointer le C du doigt ? Si nous ne devions retenir qu’un langage pour apprendre la rigueur du code, l’importance d’un code correct et irréprochable, ce serait probablement le C.

Le C force le programmeur à raisonner sur son code, sous peine de programmes qui ne veulent rien dire (undefined behavior), mais sans fournir aucun outil à l’utilisateur pour vérifier que son raisonnement est correct. Au mieux tu peux utiliser les sanitizers (qui n’existent chez le grand public que depuis une poignée d’année) et valgrind pour te dire: j’ai vérifié que sur mes tests mon programme ne semble pas rencontrer de comportement dangereux à l’exécution (ce qui ne garantit en rien l’absence d’undefined behavior, même sur les chemins testés).

Le résultat en pratique est que tous les gros programmes C sont faux (ne veulent rien dire), mais que l’on maintient collectivement une forme de tolérance en demandant aux compilateurs de ne pas être trop méchants dans ces cas-là, ou en espérant qu’ils ne le deviendront pas dans le futur.

Je préfère utiliser un langage où il est possible d’écrire du code correct, parce que le langage va m’aider à vérifier que mon raisonnement est correct (ou me simplifier la vie pour ne pas me forcer à raisonner sur tel ou tel aspect au risque de me planter), et où les cas incorrects ont des conséquences prédictibles et débuggables (par exemple, un accès hors des bornes d’un tableau se comporte mieux en Python, où on a une exception propre, qu’en C où on peut modifier des structures internes n’importe où dans le programme).

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte