La concision est puissance
Mai 2002
| "La quantité de sens compressée dans un petit espace par des signes algébriques, est une autre circonstance qui facilite les raisonnements que nous avons l'habitude de mener à leur aide."
- Charles Babbage, cité dans la conférence de réception du prix Turing d'Iverson
Dans la discussion sur les questions soulevées par La Revanche des Nerds sur la liste de diffusion LL1, Paul Prescod a écrit quelque chose qui m'est resté en tête.
L'objectif de Python est la régularité et la lisibilité, pas la concision.
À première vue, cela semble une affirmation plutôt accablante pour un langage de programmation. Autant que je puisse en juger, concision = puissance. Si c'est le cas, alors en substituant, nous obtenons
L'objectif de Python est la régularité et la lisibilité, pas la puissance.
et cela ne semble pas être un compromis (si c'en est un) que l'on voudrait faire. Ce n'est pas loin de dire que l'objectif de Python n'est pas d'être efficace en tant que langage de programmation.
La concision est-elle puissance ? Cela me semble une question importante, peut-être la plus importante pour quiconque s'intéresse à la conception de langages, et une question qu'il serait utile d'aborder directement. Je ne suis pas encore sûr que la réponse soit un simple oui, mais cela semble une bonne hypothèse pour commencer.
Hypothèse
Mon hypothèse est que la concision est puissance, ou est suffisamment proche pour qu'à l'exception d'exemples pathologiques, on puisse les traiter comme identiques.
Il me semble que la concision est la raison d'être des langages de programmation. Les ordinateurs seraient tout aussi heureux qu'on leur dise quoi faire directement en langage machine. Je pense que la principale raison pour laquelle nous nous donnons la peine de développer des langages de haut niveau est d'obtenir un effet de levier, afin de pouvoir dire (et plus important encore, penser) en 10 lignes d'un langage de haut niveau ce qui nécessiterait 1000 lignes de langage machine. En d'autres termes, le principal intérêt des langages de haut niveau est de rendre le code source plus petit.
Si un code source plus petit est le but des langages de haut niveau, et que la puissance de quelque chose est la mesure de sa capacité à atteindre son but, alors la mesure de la puissance d'un langage de programmation est la petitesse de ses programmes.
Inversement, un langage qui ne rend pas vos programmes petits fait mal ce que les langages de programmation sont censés faire, comme un couteau qui ne coupe pas bien, ou une impression illisible.
Mesures
Petit dans quel sens, cependant ? La mesure la plus courante de la taille du code est le nombre de lignes de code. Mais je pense que cette métrique est la plus courante parce qu'elle est la plus facile à mesurer. Je ne pense pas que quiconque croie vraiment que c'est le véritable test de la longueur d'un programme. Différents langages ont différentes conventions sur la quantité à mettre sur une ligne ; en C, beaucoup de lignes ne contiennent rien d'autre qu'un ou deux délimiteurs.
Un autre test facile est le nombre de caractères dans un programme, mais ce n'est pas très bon non plus ; certains langages (Perl, par exemple) utilisent simplement des identifiants plus courts que d'autres.
Je pense qu'une meilleure mesure de la taille d'un programme serait le nombre d'éléments, où un élément est tout ce qui serait un nœud distinct si vous dessiniez un arbre représentant le code source. Le nom d'une variable ou d'une fonction est un élément ; un entier ou un nombre à virgule flottante est un élément ; un segment de texte littéral est un élément ; un élément d'un motif, ou une directive de format, est un élément ; un nouveau bloc est un élément. Il y a des cas limites (est-ce que -5 est deux éléments ou un ?) mais je pense que la plupart d'entre eux sont les mêmes pour chaque langage, donc ils n'affectent pas beaucoup les comparaisons.
Cette métrique doit être étoffée, et elle pourrait nécessiter une interprétation dans le cas de langages spécifiques, mais je pense qu'elle essaie de mesurer la bonne chose, qui est le nombre de parties d'un programme. Je pense que l'arbre que vous dessineriez dans cet exercice est ce que vous devez construire dans votre tête pour concevoir le programme, et donc sa taille est proportionnelle à la quantité de travail que vous devez faire pour l'écrire ou le lire.
Conception
Ce type de métrique nous permettrait de comparer différents langages, mais ce n'est pas, du moins pour moi, sa principale valeur. La principale valeur du test de concision est de servir de guide dans la conception des langages. La comparaison la plus utile entre les langages est celle entre deux variantes potentielles du même langage. Que puis-je faire dans le langage pour rendre les programmes plus courts ?
Si la charge conceptuelle d'un programme est proportionnelle à sa complexité, et qu'un programmeur donné peut tolérer une charge conceptuelle fixe, alors cela revient à demander : que puis-je faire pour permettre aux programmeurs d'accomplir le plus de choses ? Et cela me semble identique à demander : comment puis-je concevoir un bon langage ?
(Incidemment, rien ne rend plus manifestement faux le vieil adage « tous les langages sont équivalents » que la conception de langages. Lorsque vous concevez un nouveau langage, vous comparez constamment deux langages – le langage si je faisais x, et si je ne le faisais pas – pour décider lequel est le meilleur. Si c'était vraiment une question dénuée de sens, autant lancer une pièce.)
Viser la concision semble un bon moyen de trouver de nouvelles idées. Si vous pouvez faire quelque chose qui rend de nombreux programmes différents plus courts, ce n'est probablement pas une coïncidence : vous avez probablement découvert une nouvelle abstraction utile. Vous pourriez même être capable d'écrire un programme pour aider en recherchant des motifs répétés dans le code source. Parmi d'autres langages, ceux qui ont une réputation de concision seraient ceux à examiner pour de nouvelles idées : Forth, Joy, Icon.
Comparaison
La première personne à écrire sur ces questions, à ma connaissance, fut Fred Brooks dans le Mythical Man Month. Il a écrit que les programmeurs semblaient générer à peu près la même quantité de code par jour, quel que soit le langage. Quand j'ai lu cela pour la première fois, au début de la vingtaine, ce fut une grande surprise pour moi et cela semblait avoir d'énormes implications. Cela signifiait que (a) la seule façon de développer des logiciels plus rapidement était d'utiliser un langage plus concis, et (b) quelqu'un qui prenait la peine de le faire pouvait laisser loin derrière les concurrents qui ne le faisaient pas.
L'hypothèse de Brooks, si elle est vraie, semble être au cœur même du hacking. Au fil des années, j'ai prêté une attention particulière à toute preuve que je pouvais obtenir sur la question, des études formelles aux anecdotes sur des projets individuels. Je n'ai rien vu qui le contredise.
Je n'ai pas encore vu de preuves qui me semblaient concluantes, et je ne m'y attends pas. Des études comme la comparaison des langages de programmation de Lutz Prechelt, bien que générant le type de résultats que j'attendais, ont tendance à utiliser des problèmes trop courts pour être des tests significatifs. Un meilleur test d'un langage est ce qui se passe dans les programmes qui prennent un mois à écrire. Et le seul vrai test, si vous croyez comme moi que le but principal d'un langage est d'être bon pour la pensée (plutôt que de simplement dire à un ordinateur quoi faire une fois que vous y avez pensé) est ce que de nouvelles choses vous pouvez écrire avec. Ainsi, toute comparaison de langages où vous devez respecter une spécification prédéfinie teste légèrement la mauvaise chose.
Le véritable test d'un langage est la capacité à découvrir et à résoudre de nouveaux problèmes, et non la capacité à l'utiliser pour résoudre un problème que quelqu'un d'autre a déjà formulé. Ces deux critères sont très différents. En art, des médiums comme la broderie et la mosaïque fonctionnent bien si vous savez à l'avance ce que vous voulez créer, mais sont absolument nuls si ce n'est pas le cas. Lorsque vous voulez découvrir l'image au fur et à mesure que vous la créez – comme vous devez le faire avec quelque chose d'aussi complexe qu'une image de personne, par exemple – vous devez utiliser un médium plus fluide comme le crayon, le lavis ou la peinture à l'huile. Et en effet, la façon dont les tapisseries et les mosaïques sont faites en pratique est de réaliser d'abord une peinture, puis de la copier. (Le mot « carton » était à l'origine utilisé pour décrire une peinture destinée à cet usage).
Cela signifie que nous n'aurons probablement jamais de comparaisons précises de la puissance relative des langages de programmation. Nous aurons des comparaisons exactes, mais pas pertinentes. En particulier, les études explicites visant à comparer les langages, parce qu'elles utiliseront probablement de petits problèmes et qu'elles utiliseront nécessairement des problèmes prédéfinis, auront tendance à sous-estimer la puissance des langages les plus puissants.
Les rapports de terrain, bien qu'ils soient nécessairement moins exacts que les études « scientifiques », sont susceptibles d'être plus significatifs. Par exemple, Ulf Wiger d'Ericsson a réalisé une étude qui a conclu qu'Erlang était 4 à 10 fois plus concis que le C++, et proportionnellement plus rapide pour développer des logiciels :
Les comparaisons entre les projets de développement internes à Ericsson indiquent une productivité similaire en lignes/heure, incluant toutes les phases de développement logiciel, plutôt indépendamment du langage (Erlang, PLEX, C, C++ ou Java) utilisé. Ce qui différencie alors les différents langages devient le volume du code source.
L'étude aborde également explicitement un point qui n'était qu'implicite dans le livre de Brooks (puisqu'il mesurait les lignes de code débogué) : les programmes écrits dans des langages plus puissants ont tendance à avoir moins de bugs. Cela devient une fin en soi, potentiellement plus importante que la productivité du programmeur, dans des applications comme les commutateurs réseau.
Le Test du Goût
En fin de compte, je pense qu'il faut se fier à son instinct. Que ressent-on en programmant dans le langage ? Je pense que la façon de trouver (ou de concevoir) le meilleur langage est de devenir hypersensible à la façon dont un langage vous permet de penser, puis de choisir/concevoir le langage qui vous semble le meilleur. Si une fonctionnalité du langage est maladroite ou restrictive, ne vous inquiétez pas, vous le saurez.
Une telle hypersensibilité aura un coût. Vous constaterez que vous ne supportez pas de programmer dans des langages maladroits. Je trouve insupportablement restrictif de programmer dans des langages sans macros, tout comme quelqu'un habitué au typage dynamique trouve insupportablement restrictif de devoir revenir à la programmation dans un langage où il faut déclarer le type de chaque variable et ne peut pas créer une liste d'objets de types différents.
Je ne suis pas le seul. Je connais de nombreux hackers Lisp à qui cela est arrivé. En fait, la mesure la plus précise de la puissance relative des langages de programmation pourrait être le pourcentage de personnes qui connaissent le langage et qui accepteront n'importe quel travail où elles pourront utiliser ce langage, quel que soit le domaine d'application.
Caractère Restrictif
Je pense que la plupart des hackers savent ce que signifie pour un langage de se sentir restrictif. Que se passe-t-il lorsque vous ressentez cela ? Je pense que c'est le même sentiment que l'on éprouve lorsque la rue que l'on veut prendre est bloquée, et que l'on doit faire un long détour pour arriver là où l'on voulait aller. Il y a quelque chose que vous voulez dire, et le langage ne vous le permet pas.
Ce qui se passe réellement ici, je pense, c'est qu'un langage restrictif est un langage qui n'est pas assez concis. Le problème n'est pas simplement que vous ne pouvez pas dire ce que vous aviez prévu. C'est que le détour que le langage vous fait prendre est plus long. Essayez cette expérience de pensée. Supposons qu'il y ait un programme que vous vouliez écrire, et que le langage ne vous permette pas de l'exprimer comme vous l'aviez prévu, mais vous force plutôt à écrire le programme d'une autre manière qui était plus courte. Pour moi du moins, cela ne semblerait pas très restrictif. Ce serait comme la rue que vous vouliez prendre étant bloquée, et le policier à l'intersection vous dirigeant vers un raccourci au lieu d'un détour. Génial !
Je pense que la majeure partie (quatre-vingt-dix pour cent ?) du sentiment de restriction vient du fait d'être forcé de rendre le programme que vous écrivez dans le langage plus long que celui que vous avez en tête. Le caractère restrictif est principalement un manque de concision. Ainsi, lorsqu'un langage semble restrictif, cela signifie (principalement) qu'il n'est pas assez concis, et lorsqu'un langage n'est pas concis, il semblera restrictif.
Lisibilité
La citation par laquelle j'ai commencé mentionne deux autres qualités, la régularité et la lisibilité. Je ne suis pas sûr de ce qu'est la régularité, ni de l'avantage, le cas échéant, qu'un code régulier et lisible a sur un code simplement lisible. Mais je pense savoir ce que l'on entend par lisibilité, et je pense que c'est aussi lié à la concision.
Nous devons être prudents ici pour distinguer la lisibilité d'une ligne de code individuelle et la lisibilité de l'ensemble du programme. C'est la seconde qui compte. Je suis d'accord qu'une ligne de Basic est susceptible d'être plus lisible qu'une ligne de Lisp. Mais un programme écrit en Basic aura plus de lignes que le même programme écrit en Lisp (surtout une fois que vous entrez dans le Greenspunland). L'effort total de lecture du programme Basic sera sûrement plus grand.
effort total = effort par ligne x nombre de lignes
Je ne suis pas aussi sûr que la lisibilité soit directement proportionnelle à la concision que je le suis que la puissance l'est, mais la concision est certainement un facteur (au sens mathématique ; voir l'équation ci-dessus) de la lisibilité. Il pourrait donc même ne pas être significatif de dire que le but d'un langage est la lisibilité, et non la concision ; ce serait comme dire que le but était la lisibilité, et non la lisibilité.
Ce que la lisibilité par ligne signifie, pour l'utilisateur qui découvre le langage, c'est que le code source semblera inoffensif. Ainsi, la lisibilité par ligne pourrait être une bonne décision marketing, même si c'est une mauvaise décision de conception. C'est isomorphe à la technique très réussie de laisser les gens payer par versements : au lieu de les effrayer avec un prix initial élevé, vous leur indiquez le faible paiement mensuel. Les plans de versement sont cependant une perte nette pour l'acheteur, tout comme la simple lisibilité par ligne l'est probablement pour le programmeur. L'acheteur va faire beaucoup de ces petits paiements ; et le programmeur va lire beaucoup de ces lignes individuellement lisibles.
Ce compromis est antérieur aux langages de programmation. Si vous êtes habitué à lire des romans et des articles de journaux, votre première expérience de lecture d'un article de mathématiques peut être décourageante. Il pourrait vous falloir une demi-heure pour lire une seule page. Et pourtant, je suis presque sûr que la notation n'est pas le problème, même si cela peut en donner l'impression. L'article de mathématiques est difficile à lire parce que les idées sont difficiles. Si vous exprimiez les mêmes idées en prose (comme les mathématiciens devaient le faire avant d'évoluer vers des notations concises), elles ne seraient pas plus faciles à lire, car l'article prendrait la taille d'un livre.
Dans Quelle Mesure ?
Un certain nombre de personnes ont rejeté l'idée que concision = puissance. Je pense qu'il serait plus utile, au lieu de simplement argumenter qu'elles sont identiques ou non, de demander : dans quelle mesure la concision = puissance ? Car il est clair que la concision est une grande partie de la raison d'être des langages de haut niveau. Si ce n'est pas tout ce à quoi ils servent, alors à quoi d'autre servent-ils, et quelle est l'importance relative de ces autres fonctions ?
Je ne propose pas cela juste pour rendre le débat plus civilisé. Je veux vraiment connaître la réponse. Quand, si jamais, un langage est-il trop concis pour son propre bien ?
L'hypothèse avec laquelle j'ai commencé était que, sauf dans des exemples pathologiques, je pensais que la concision pouvait être considérée comme identique à la puissance. Ce que je voulais dire, c'est que dans tout langage que quiconque concevrait, elles seraient identiques, mais que si quelqu'un voulait concevoir un langage explicitement pour réfuter cette hypothèse, il pourrait probablement le faire. Je n'en suis même pas sûr, en fait.
Les Langages, pas les Programmes
Nous devons être clairs : nous parlons de la concision des langages, et non des programmes individuels. Il est certainement possible que des programmes individuels soient écrits de manière trop dense.
J'ai écrit à ce sujet dans On Lisp. Une macro complexe peut devoir économiser plusieurs fois sa propre longueur pour être justifiée. Si l'écriture d'une macro complexe pouvait vous faire économiser dix lignes de code chaque fois que vous l'utilisez, et que la macro elle-même fait dix lignes de code, alors vous obtenez une économie nette de lignes si vous l'utilisez plus d'une fois. Mais cela pourrait quand même être une mauvaise décision, car les définitions de macros sont plus difficiles à lire que le code ordinaire. Vous pourriez devoir utiliser la macro dix ou vingt fois avant qu'elle n'apporte une amélioration nette de la lisibilité.
Je suis sûr que chaque langage a de tels compromis (bien que je soupçonne que les enjeux augmentent à mesure que le langage devient plus puissant). Chaque programmeur a dû voir du code qu'une personne astucieuse a rendu marginalement plus court en utilisant des astuces de programmation douteuses.
Donc, il n'y a pas de débat là-dessus – du moins, pas de ma part. Les programmes individuels peuvent certainement être trop concis pour leur propre bien. La question est : un langage peut-il l'être ? Un langage peut-il obliger les programmeurs à écrire du code court (en éléments) au détriment de la lisibilité globale ?
Une raison pour laquelle il est difficile d'imaginer qu'un langage soit trop concis est que s'il existait une manière excessivement compacte de formuler quelque chose, il y aurait probablement aussi une manière plus longue. Par exemple, si vous trouviez que les programmes Lisp utilisant beaucoup de macros ou de fonctions d'ordre supérieur étaient trop denses, vous pourriez, si vous préfériez, écrire du code isomorphe à du Pascal. Si vous ne voulez pas exprimer la factorielle en Arc comme un appel à une fonction d'ordre supérieur (rec zero 1 * 1-) vous pouvez aussi écrire une définition récursive : (rfn fact (x) (if (zero x) 1 (* x (fact (1- x))))) Bien que je ne puisse pas penser à des exemples sur le champ, je suis intéressé par la question de savoir si un langage pourrait être trop concis. Existe-t-il des langages qui vous forcent à écrire du code d'une manière alambiquée et incompréhensible ? Si quelqu'un a des exemples, je serais très intéressé de les voir.
(Rappel : Ce que je cherche, ce sont des programmes très denses selon la métrique des « éléments » esquissée ci-dessus, et non pas simplement des programmes courts parce que les délimiteurs peuvent être omis et que tout a un nom d'un seul caractère.)