1 points par GN⁺ 4 시간 전 | 1 commentaires | Partager sur WhatsApp
  • La baisse de popularité des types statiques entre les années 2000 et le début des années 2010, puis leur regain d’intérêt au milieu et à la fin des années 2010, s’expliquent par l’amélioration de la qualité des systèmes de types statiques
  • Les systèmes de types dynamiques sont comparés au fait de creuser la terre à mains nues : il faut juger soi-même l’état et le contenu des variables et des champs, sans que l’ordinateur n’aide ni n’entrave ce travail
  • Les anciens systèmes de types statiques, comme ceux du Java des débuts ou de C++98, sont comparés à une pelle en papier : ils n’aident même pas à distinguer les pointeurs nullable et non-nullable, et obligent à répéter les noms de types
  • Les systèmes de types modernes comme TypeScript, Haskell, MyPy, Swift et Rust prennent mieux en charge la gestion de null, les types somme et les types union, ainsi que l’inférence de types, ce qui améliore la détection d’erreurs et l’expression des états d’un programme
  • Avec la généralisation de fonctions d’IDE comme l’autocomplétion des noms de méthodes, les informations fournies au système de types statiques apportent non seulement des vérifications d’erreurs, mais aussi des gains de productivité

Argument principal

  • L’idée est que le retour en grâce des types statiques ne relève pas d’un simple effet de mode, mais du fait que la qualité des systèmes de types statiques réellement utilisables s’est améliorée
  • L’article utilise l’analogie suivante : avec une bonne pelle, mieux vaut creuser avec une pelle qu’à mains nues ; mais si l’on ne dispose que d’une pelle en papier, alors les mains nues valent mieux
  • Dans un système de types dynamiques, c’est au développeur de réfléchir lui-même à l’état et au contenu des variables et des champs d’un programme, sans aide de l’ordinateur
  • Un mauvais système de types statiques peut devenir plus contraignant qu’utile, ce qui est comparé au fait de creuser avec une pelle en papier

Limites des anciens systèmes de types statiques

  • Les premiers systèmes de types statiques largement utilisés dans les années 1990 et au début des années 2000, comme ceux de Java ou de C++98, n’aidaient même pas correctement pour une tâche simple comme distinguer les pointeurs nullable et non-nullable
  • Les anciens systèmes de types statiques sont décrits comme des structures sans types somme, avec uniquement des types produit
  • Ils forçaient à écrire manuellement les noms de types à de nombreux endroits
  • Un code comme BufferedReader bufferedReader = new BufferedReader(new FileReader(filename)); est qualifié de petite catastrophe

Améliorations des systèmes de types modernes

  • Les systèmes de types modernes comme TypeScript, Haskell, MyPy, Swift et Rust proposent des moyens de distinguer les types nullable des types non-nullable
  • Les exemples donnés sont Maybe t en Haskell, T | null en TypeScript, T? en Swift et Optional<T> en Rust ; le système de types peut alors signaler les endroits où une vérification de null est nécessaire et ceux où elle manque
  • En pratique, cela permettrait de ne presque plus voir d’erreurs de pointeur nul à l’exécution
  • Les systèmes de types modernes offrent au moins l’un des deux mécanismes suivants, souvent les deux : types somme ou types union, ce qui permet de mettre en pratique "Make invalid states unrepresentable"
  • Cette approche permet, dans des objets représentant des machines à états, de n’exprimer chaque champ que lorsqu’il existe réellement pour l’état concerné
  • Les systèmes de types modernes fournissent aussi l’inférence de types : si le compilateur peut comprendre que let x = 5; est un nombre, il n’est pas nécessaire d’écrire let x: number = 5;

Fonctionnalités d’IDE et conclusion

  • Avec la généralisation des fonctions d’IDE comme l’autocomplétion des noms de méthodes, l’utilité des systèmes de types statiques a encore augmenté
  • Dans les années 1990, Intellisense était une fonctionnalité phare de Visual Studio ; dans les années 2020, des fonctions similaires existent dans presque tous les IDE et éditeurs
  • Les informations fournies au système de types statiques servent non seulement à vérifier les erreurs dans le programme, mais apportent aussi des gains de productivité supplémentaires
  • Un bon système de types dynamiques vaut mieux qu’un mauvais système de types statiques, mais aujourd’hui on peut utiliser des systèmes de types statiques bien meilleurs qu’autrefois

1 commentaires

 
GN⁺ 4 시간 전
Avis sur Lobste.rs
  • Cet article est bon, mais je ne suis pas totalement d’accord. Même si les systèmes de types statiques du début des années 2000 n’étaient pas excellents, ils étaient selon moi bien préférables à l’absence totale de typage statique
    Il n’y avait pas de types somme fermés, mais on pouvait modéliser une grande partie avec le sous-typage, et il n’y avait pas de types non-null, mais les références et types non pointeurs de C++, ainsi que les types primitifs de Java, en couvraient une partie. En Ruby ou JavaScript, non seulement tous les types pouvaient être null, mais ils pouvaient aussi être traités comme des chaînes, comme des entiers, ou comme n’importe quel autre type du programme, ce qui était pire
    Je pense qu’un grand facteur du changement de tendance autour du typage statique est que, pendant le boom des réseaux sociaux du Web 2.0, l’avantage du premier arrivé comptait plus que tout. Mieux valait lancer vite et itérer, quitte à accumuler de la dette technique en Ruby ou Python, que de se faire dépasser comme Friendster ou Digg, et si c’était lent, on pouvait simplement acheter plus de serveurs avec l’argent à bas taux facilement disponible à l’époque
    Ensuite, avec le boom mobile, les logiciels se sont mis à tourner sur des appareils utilisateurs limités et hors de contrôle, et les applications à typage dynamique lentes étaient tout simplement lentes en pratique, tandis qu’en cas d’erreur de type on ne pouvait pas se rétablir proprement via un gestionnaire global de réponse comme sur un serveur. Dans ce contexte, la sécurité et les performances du typage statique sont devenues bien plus convaincantes

    • Il existe pas mal d’articles comparant Java et le C++ des années 90 à des bases de code en langages à typage dynamique, concluant à des taux de bugs similaires, et les partisans des langages dynamiques s’en servent souvent pour affirmer que le typage statique n’est pas utile
      Au début des années 2000, j’étais d’accord aussi, car les systèmes de types de l’époque imposaient souvent des contraintes qui n’aidaient pas à structurer le code, tout en ne garantissant que des propriétés presque jamais erronées. En particulier, la combinaison du sous-typage et de l’héritage d’implémentation manquait de souplesse
      J’ai changé d’avis en utilisant des systèmes de types plus modernes. Dans snmalloc, le système de types C++ impose une machine à états de propriété mémoire, et dans d’autres bases de code il vérifie le bon comportement d’overflow de compteurs de buffers circulaires. Dans les deux cas, quand on se trompe, le débogage est pénible et c’est une source d’erreurs fréquente, mais le compilateur a réellement refusé de compiler du code que je pensais correct, empêchant ainsi des bugs d’entrer dans la branche principale
    • Je pense que développer dans un langage à typage dynamique est plus lent que dans un langage à typage statique. Je vois sans cesse l’argument inverse, mais je ne le comprends pas
      Dans un IDE, appuyer sur . puis taper quelques lettres du nom de la méthode avant de valider la bonne suggestion permet d’économiser 2 secondes toutes les quelques secondes, et fait aussi gagner les 30 secondes qu’il faudrait autrement pour aller chercher la définition d’une classe quand on ne sait pas quelles méthodes existent. Ce principe est aussi bien décrit sur https://grugbrain.dev/#grug-on-type-systems
      On écrit bien plus souvent des lignes qui appellent des méthodes que des annotations de type de paramètres de fonctions, donc le compromis est massivement défavorable au typage dynamique. Ce qui avait de la valeur, ce n’était pas d’autoriser du code absurde qui échoue à l’exécution, mais de pouvoir omettre le type des variables locales, et les langages à typage statique n’ont jamais eu besoin de l’interdire
    • Les systèmes de types populaires du début des années 2000 n’étaient pas juste “pas incroyables” : ils étaient mauvais, et très verbeux
      Dans les rares bases de code qui prenaient le système de types au sérieux, on accumulait des pages de code qui ne disaient rien, tout en gardant des montagnes de conditions d’exécution, et dans le cas de Java, plus la hiérarchie de types grossissait, plus le programme devenait concrètement lent. La plupart des bases de code utilisaient les types de manière incomplète tout en ajoutant beaucoup de conditions à l’exécution, sans réduire énormément la couverture de tests nécessaire par rapport à un système dynamique
      Les langages dynamiques n’apportaient aucun bénéfice statique, mais ils étaient concis, donc plus faciles à lire, à relire et à tester. C’était particulièrement vrai dans des environnements comme les frameworks d’injection de dépendances de la fin des années 90 et du début des années 2000, où l’ajout d’un nouveau service imposait de modifier plusieurs fichiers XML. On pouvait aussi travailler sans IDE consommant la moitié de la RAM
      C’est exactement à ça qu’a ressemblé le début de ma carrière, donc je suis entièrement d’accord avec l’article. Le rapport coût/bénéfice de Java 1.4 à Java 6 était tellement mauvais que j’ai presque abandonné les langages à typage statique, et ce n’est que quelques années plus tard, en touchant à Haskell comme hobby, que j’ai compris que le typage statique pouvait lui aussi avoir un rapport coût/bénéfice raisonnable, et que le problème, c’était Java. L’essai “python is not java” illustre aussi très bien cette période sombre
    • Le sous-typage fondé sur l’héritage était encore plus pauvre. On n’obtenait pas l’ergonomie du pattern matching avec vérification d’exhaustivité, et l’implémentation se retrouvait éparpillée à plusieurs endroits
    • L’explication selon laquelle il était crucial de mettre son site en ligne avant ses concurrents, de le placer devant les utilisateurs et de verrouiller les économies d’échelle paraît aussi très familière dans la situation actuelle
  • Je doute qu’on ait réellement constaté dans nos logiciels des gains de fiabilité depuis que le typage statique est devenu l’air du temps
    J’ai toujours pensé que les avantages du typage statique tenaient bien davantage au feedback immédiat pendant le développement et à la réduction des échecs critiques à l’exécution, mais même si de tels échecs restent théoriquement possibles, j’ai l’impression qu’en pratique ils ne surviennent pas si souvent

    • Si. À partir du moment où nous avons commencé à viser zéro erreur TypeScript dans une base de code TypeScript non triviale, les tentatives d’appel de méthodes sur undefined et null ont chuté brutalement
      Des juniors et même certains seniors étaient sceptiques au début, pensant qu’on allait se retrouver avec des @ts-ignore partout, mais en pratique il n’y en a eu qu’environ trois, y compris ceux dus à des types cassés dans des dépendances. Avant, il arrivait environ une fois par semaine qu’un conflit de types fasse planter l’app sur la branche de développement et bloque mon travail ; maintenant, je ne me souviens même plus de la dernière fois que c’est arrivé
      Le simple fait de satisfaire tsc réduit les bugs liés aux types, y compris quand le code n’a pas été écrit par moi. À l’inverse, les linters sont devenus trop zélés ces temps-ci, et à force de vouloir satisfaire des outils comme Sonar, j’ai vu de vraies régressions de refactoring. 95 % des alertes étaient fausses, 3 % venaient de bugs de l’outil, et les 2 % utiles ne pointaient même pas vers la vraie cause des bugs. Au lieu de passer une semaine à aligner la base de code pour corriger un bug, j’en ai ajouté deux autres au passage
      Le travail pour satisfaire tsc produisait en gros deux corrections de bugs purs et une régression par jour, mais les régressions étaient en général moins graves, plutôt du mauvais comportement qu’un crash complet
      En y ajoutant des tests basés sur les propriétés, cela prenait en moyenne 2 à 4 heures et révélait toujours au moins un bug. Si votre code se prête aux tests basés sur les propriétés, il faut en faire
      En augmentant la couverture de test avec le modèle bon marché DeepSeek V4 Flash tout en faisant attention à ne pas générer de tests poubelle, j’ai corrigé environ 2 à 3 bugs logiques par jour, sans crash. En revanche, le lot de tests reste tout juste maintenable
      Quand on a laissé des juniors bricoler des tests avec des modèles de la famille Sonnet et Opus 4.5, 4.6, les modèles n’ont produit que des tests qui « documentaient le comportement actuel », donc l’effet des corrections a été faible, et le lot de tests était impossible à maintenir, au point qu’il a fallu le jeter
      Les tests basés sur des modèles sont très bons pour attraper des bugs, mais la configuration est complexe, et il est très pénible de les pousser à explorer les recoins plutôt que de brûler des cycles sur des fonctionnalités de surface. Un genre de fuzzer basé sur des modèles et piloté par profil serait intéressant
      En résumé, les vérificateurs de types attrapent bien les défaillances critiques et nombre de confusions, et les tests basés sur les propriétés sont excellents. Les tests classiques demandent beaucoup de discipline pour offrir un bénéfice régulier
    • Personnellement, oui. Dans le JavaScript que j’utilise, les bugs de pointeur nul sont devenus presque négligeables après le passage à TypeScript, et mes collègues ont eu la même expérience
  • C’est surtout l’assimilation de TypeScript à un bon système de types avec laquelle j’ai du mal à être d’accord

    • Oui. TypeScript n’est pas sain, et en particulier sa manière de restreindre les types à travers await m’a mordu plusieurs fois. Cela dit, il a bel et bien amélioré la situation de façon spectaculaire
      Honnêtement, j’ai fini par accepter le typage structurel aussi, et je pense que cela aura une influence positive sur la conception des langages à venir
  • Cet argument n’est pas très convaincant. Des langages de programmation corrects avec types de données algébriques et inférence de types existaient déjà depuis le milieu des années 1990
    Les systèmes de types de Java et C++ étaient très pauvres, mais SML, OCaml et Haskell existaient déjà, avec un ressenti assez proche d’aujourd’hui. Si les gens n’utilisaient pas ces langages, c’est un problème de culture, d’adoption et d’exigences implicites, pas quelque chose qu’on peut expliquer uniquement par « les systèmes de types utilisables n’étaient pas assez bons »
    Ou bien, si l’argument est « les systèmes de types des langages populaires de l’époque étaient mauvais, et ceux des langages populaires d’aujourd’hui sont meilleurs, donc les systèmes de types sont devenus plus populaires », cela ressemble à un raisonnement circulaire
    Il y a aussi énormément de nuances dans la différence entre les langages conçus avec un système de types dès le départ et ceux conçus sans types à l’origine, puis dotés d’un système de types plus tard

  • Même en ayant toujours eu une préférence pour le typage dynamique, je trouve cet article assez juste. Aujourd’hui je travaille en C#, j’utilise Lisp pour mes loisirs, et j’ai aussi utilisé Python auparavant
    Quand j’étais obligé d’utiliser Java 5, je me battais en permanence contre le système de types, généralement à cause de mauvaises décisions prises par les auteurs de bibliothèques. Après être passé à C# vers 2010, le système de types n’était plus activement nuisible, mais restait pour l’essentiel redondant, et il n’empêchait même pas les exceptions de pointeur nul, qui étaient la confusion de types la plus fréquente en Python
    Le système de types de C# n’a vraiment commencé à m’aider qu’autour de 2020, avec l’arrivée des types de référence non nullables. Cette année, il accueille aussi des types union natifs, mais des bibliothèques de types union imposant l’exhaustivité étaient possibles au moins depuis 2016, et j’ai commencé à en utiliser à partir de 2020
    Je pense que l’effet de mode joue encore un rôle, mais qu’une partie de cet effet n’est pas mauvaise. Des langages à la mode dotés de systèmes de types plus expressifs ont aussi apporté des améliorations aux langages ordinaires que nous utilisons au travail contre rémunération

  • Haskell et son système de types existaient déjà dans les années 2000. Ce n’était pas aussi largement utilisé qu’aujourd’hui, mais cela existait clairement, donc cette affirmation devrait être nuancée sur ce point
    Personnellement, je pense que TypeScript a été un facteur majeur pour familiariser les utilisateurs de langages grand public avec de meilleurs systèmes de types. Au-delà de sa qualité et du soutien de Microsoft, il avait l’avantage de s’appliquer à JavaScript, et JavaScript avait plus urgemment besoin de types que Python. À cause de « Undefined is not a function. » et de « The good parts. »

    • J’aimerais bien qu’un livre « good parts » adapté aux versions récentes de JavaScript sorte tout en restant concis
      « Real World Haskell » est sorti en 2008 et visait à rendre Haskell plus attractif pour les programmeurs grand public. Je ne sais pas dans quelle mesure cela a aidé à diffuser la bonne parole
      Dans l’univers Java, Scala a apporté des types sophistiqués en 2004, et .NET a eu F# en 2005. Scala a peut-être obtenu les utilisateurs les plus visibles, comme Twitter, mais il n’était pas dans une position lui permettant d’absorber une grande part des utilisateurs de sa plateforme comme TypeScript, ni assez attractif pour attirer massivement des utilisateurs d’autres langages comme Rust ou Go
    • Le texte traite déjà de cette question. Il compare les systèmes de types statiques pauvres des débuts de Java, ou de C++98, populaires dans les années 1990 et au début des années 2000, à des pelles en papier
      Dans le paragraphe suivant, il mentionne Haskell comme un « système de types moderne », mais à la fin des années 1990 et au début des années 2000, les personnes ayant une expérience de Haskell — même simplement pour y avoir touché un peu — représentaient en pratique presque 0 %. Le texte parle de la manière dont l’immense majorité des développeurs faisait alors l’expérience des langages à typage statique, et de la raison pour laquelle cette majorité les évitait collectivement
    • Je pense que Haskell et OCaml souffrent dans une certaine mesure d’un écosystème d’outils faible. Les langages eux-mêmes sont excellents, mais ils perdent en adoption à cause d’une multitude de petites coupures de papier côté outils
      Par exemple, pour utiliser dune en OCaml, il faut comprendre les fichiers opam, les fichiers dune, la syntaxe des ocaml module et la syntaxe d’ocaml. Les extensions de compilateur optionnelles de Haskell me paraissent tout aussi intimidantes
      À comparer avec cargo, où il suffit de connaître le toml et Rust