LA REVANCHE DES NERDS
OriginalMai 2002
« Nous recherchions des programmeurs C++. Nous avons réussi à en attirer un grand nombre à mi-chemin vers Lisp. »
- Guy Steele, co-auteur de la spécification Java
Dans le secteur des logiciels, il y a une lutte permanente entre les universitaires à la tête pointue et une autre force tout aussi redoutable, les patrons aux cheveux pointus. Tout le monde sait qui est le patron aux cheveux pointus, n'est-ce pas ? Je pense que la plupart des gens dans le monde de la technologie reconnaissent non seulement ce personnage de dessin animé, mais connaissent également la personne réelle de leur entreprise sur laquelle il s'est basé.
Le patron aux cheveux pointus combine miraculeusement deux qualités qui sont communes entre elles, mais rarement vues ensemble : (a) il ne sait absolument rien de la technologie, et (b) il a des opinions très tranchées à ce sujet.
Supposons, par exemple, que vous ayez besoin d'écrire un logiciel. Le patron aux cheveux pointus n'a aucune idée de la manière dont ce logiciel doit fonctionner et ne sait pas distinguer un langage de programmation d'un autre, et pourtant il sait dans quel langage vous devez l'écrire. Exactement. Il pense que vous devriez l'écrire en Java.
Pourquoi pense-t-il cela ? Jetons un œil à l'intérieur du cerveau du patron aux cheveux pointus. Ce qu'il pense est quelque chose comme ça. Java est un standard. Je sais que ça doit l'être, car j'en entends parler dans la presse tout le temps. Comme c'est un standard, je n'aurai pas d'ennuis pour l'avoir utilisé. Et cela signifie aussi qu'il y aura toujours beaucoup de programmeurs Java, donc si les programmeurs qui travaillent pour moi maintenant démissionnent, comme le font toujours mystérieusement les programmeurs qui travaillent pour moi, je peux facilement les remplacer.
Bon, ça ne semble pas si déraisonnable. Mais tout cela repose sur une hypothèse tacite, et cette hypothèse s'avère fausse. Le patron aux cheveux pointus croit que tous les langages de programmation sont plus ou moins équivalents. Si c'était vrai, il aurait raison. Si tous les langages sont équivalents, bien sûr, utilisez le langage que tout le monde utilise.
Mais tous les langages ne sont pas équivalents, et je pense pouvoir vous le prouver sans même aborder les différences entre eux. Si vous aviez demandé au patron aux cheveux pointus en 1992 dans quel langage les logiciels devaient être écrits, il aurait répondu avec aussi peu d'hésitation qu'aujourd'hui. Les logiciels devaient être écrits en C++. Mais si les langages sont tous équivalents, pourquoi l'opinion du patron aux cheveux pointus devrait-elle changer ? En fait, pourquoi les développeurs de Java se sont-ils donné la peine de créer un nouveau langage ?
Si vous créez un nouveau langage, c'est probablement parce que vous pensez qu'il est meilleur que ce que les gens avaient déjà. En fait, Gosling explique clairement dans le premier livre blanc sur Java que Java a été conçu pour résoudre certains problèmes du C++. Voilà donc ce que vous devez savoir : les langages ne sont pas tous équivalents. Si vous remontez la piste du cerveau du patron aux cheveux pointus jusqu'à Java, puis remontez l'histoire de Java jusqu'à ses origines, vous finissez par avoir une idée qui contredit l'hypothèse de départ.
Alors, qui a raison ? James Gosling ou le patron aux cheveux pointus ? Sans surprise, Gosling a raison. Certains langages sont meilleurs que d'autres pour certains problèmes. Et vous savez, cela soulève des questions intéressantes. Java a été conçu pour être meilleur que C++ pour certains problèmes. Quels problèmes ? Quand Java est-il meilleur et quand est-ce C++ ? Existe-t-il des situations dans lesquelles d'autres langages sont meilleurs que l'un ou l'autre ?
Dès que vous commencez à réfléchir à cette question, vous avez ouvert une véritable boîte de Pandore. Si le patron aux cheveux pointus devait réfléchir au problème dans toute sa complexité, son cerveau exploserait. Tant qu'il considère tous les langages comme équivalents, il n'a qu'à choisir celui qui lui semble avoir le plus de succès, et comme c'est plus une question de mode que de technologie, même lui peut probablement trouver la bonne réponse. Mais si les langages varient, il doit soudainement résoudre deux équations simultanées, en essayant de trouver un équilibre optimal entre deux choses qu'il ne connaît pas : l'adéquation relative de la vingtaine de langages phares au problème qu'il doit résoudre, et les chances de trouver des programmeurs, des bibliothèques, etc. pour chacun. Si c'est ce qui se trouve de l'autre côté de la porte, il n'est pas surprenant que le patron aux cheveux pointus ne veuille pas l'ouvrir.
L'inconvénient de croire que tous les langages de programmation sont équivalents est que ce n'est pas vrai. Mais l'avantage est que cela simplifie grandement la vie. Et je pense que c'est la principale raison pour laquelle cette idée est si répandue. C'est une idée confortable .
Nous savons que Java doit être plutôt bon, car c'est le nouveau langage de programmation cool. Ou est-ce le cas ? Si vous regardez le monde des langages de programmation de loin, il semble que Java soit la dernière nouveauté. (De loin, tout ce que vous pouvez voir est le grand panneau d'affichage clignotant payé par Sun.) Mais si vous regardez ce monde de près, vous découvrez qu'il y a des degrés de coolitude. Dans la sous-culture des hackers, il existe un autre langage appelé Perl qui est considéré comme beaucoup plus cool que Java. Slashdot, par exemple, est généré par Perl. Je ne pense pas que vous trouveriez ces gars-là utilisant Java Server Pages. Mais il existe un autre langage, plus récent, appelé Python, dont les utilisateurs ont tendance à mépriser Perl, et d'autres attendent dans les coulisses.
Si vous regardez ces langages dans l'ordre, Java, Perl, Python, vous remarquerez un schéma intéressant. Du moins, vous remarquerez ce schéma si vous êtes un hacker Lisp. Chacun d'eux ressemble de plus en plus à Lisp. Python copie même des fonctionnalités que de nombreux hackers Lisp considèrent comme des erreurs. Vous pourriez traduire des programmes Lisp simples en Python ligne par ligne. Nous sommes en 2002 et les langages de programmation ont presque rattrapé leur retard de 1958.
Rattraper son retard en mathématiques
Ce que je veux dire, c'est que Lisp a été découvert par John McCarthy en 1958, et les langages de programmation populaires ne commencent qu'à rattraper les idées qu'il a développées à l'époque.
Comment cela peut-il être vrai ? La technologie informatique n'est-elle pas quelque chose qui évolue très rapidement ? En 1958, les ordinateurs étaient des mastodontes de la taille d'un réfrigérateur avec la puissance de traitement d'une montre-bracelet. Comment une technologie aussi ancienne pourrait-elle être pertinente, et encore moins supérieure aux derniers développements ?
Je vais vous dire comment. C'est parce que Lisp n'a pas vraiment été conçu pour être un langage de programmation, du moins pas au sens où nous l'entendons aujourd'hui. Ce que nous entendons par langage de programmation, c'est quelque chose que nous utilisons pour dire à un ordinateur ce qu'il doit faire. McCarthy avait effectivement l'intention de développer un langage de programmation dans ce sens, mais le Lisp que nous avons finalement obtenu était basé sur quelque chose de différent qu'il a fait comme exercice théorique - un effort pour définir une alternative plus pratique à la machine de Turing. Comme McCarthy l'a dit plus tard,
Une autre façon de montrer que Lisp était plus propre que les machines de Turing était d'écrire une fonction Lisp universelle et de montrer qu'elle était plus brève et plus compréhensible que la description d'une machine de Turing universelle. Il s'agissait de la fonction Lisp eval ..., qui calcule la valeur d'une expression Lisp.... L'écriture de eval a nécessité l'invention d'une notation représentant les fonctions Lisp sous forme de données Lisp, et une telle notation a été conçue pour les besoins de l'article sans penser qu'elle serait utilisée pour exprimer des programmes Lisp dans la pratique.
Ce qui s'est passé ensuite, c'est que, vers la fin de 1958, Steve Russell, l'un des étudiants diplômés de McCarthy, a examiné cette définition de eval et s'est rendu compte que s'il la traduisait en langage machine, le résultat serait un interpréteur Lisp.
Ce fut une grande surprise à l'époque. Voici ce que McCarthy a déclaré à ce sujet plus tard dans une interview :
Steve Russell m'a dit : "Regarde, pourquoi ne pas programmer cet eval ... ", et je lui ai répondu : "Oh, oh, tu confonds la théorie avec la pratique, cet eval est destiné à la lecture, pas au calcul". Mais il a continué et l'a fait. C'est-à-dire qu'il a compilé l' eval de mon article en code machine [IBM] 704, en corrigeant les bugs, puis il a fait la publicité de ce code comme étant un interpréteur Lisp, ce qu'il était certainement. Donc à ce moment-là, Lisp avait essentiellement la forme qu'il a aujourd'hui...
Soudain, en quelques semaines je crois, McCarthy a vu son exercice théorique transformé en un véritable langage de programmation – et plus puissant que ce qu’il avait prévu.
En bref, la raison pour laquelle ce langage des années 1950 n'est pas obsolète est qu'il ne s'agissait pas de technologie mais de mathématiques, et les mathématiques ne vieillissent jamais. La bonne chose à comparer à Lisp n'est pas le matériel des années 1950, mais, par exemple, l'algorithme Quicksort, qui a été découvert en 1960 et qui est toujours le tri généraliste le plus rapide.
Il existe un autre langage des années 1950 qui a survécu, le Fortran, et il représente l'approche opposée de la conception de langage. Lisp était un élément théorique qui a été transformé de manière inattendue en langage de programmation. Le Fortran a été développé intentionnellement comme un langage de programmation, mais ce que nous considérons aujourd'hui comme un langage de très bas niveau.
Fortran I , le langage développé en 1956, était très différent du Fortran actuel. Fortran I était en quelque sorte un langage d'assemblage avec des mathématiques. À certains égards, il était moins puissant que les langages d'assemblage plus récents ; il n'y avait pas de sous-programmes, par exemple, seulement des branches. Le Fortran actuel est sans doute plus proche de Lisp que de Fortran I.
Lisp et Fortran étaient les troncs de deux arbres évolutifs distincts, l'un ancré dans les mathématiques et l'autre dans l'architecture des machines. Ces deux arbres convergent depuis lors. Lisp était puissant au départ, et au cours des vingt années suivantes, il est devenu rapide. Les langages dits « mainstream » ont démarré rapidement, et au cours des quarante années suivantes, ils sont devenus progressivement plus puissants, jusqu'à ce que les plus avancés d'entre eux soient assez proches de Lisp. Proches, mais il leur manque encore quelques éléments...
Ce qui rend Lisp différent
Lors de son développement initial, Lisp incorporait neuf idées nouvelles. Certaines d'entre elles sont aujourd'hui considérées comme allant de soi, d'autres ne sont présentes que dans des langages plus avancés et deux sont encore propres à Lisp. Les neuf idées sont, dans l'ordre de leur adoption par le grand public,
Conditionnels. Un conditionnel est une construction if-then-else. Nous les tenons pour acquis maintenant, mais Fortran I ne les avait pas. Il n'avait qu'un goto conditionnel étroitement basé sur l'instruction machine sous-jacente.
Un type de fonction. En Lisp, les fonctions sont un type de données tout comme les entiers ou les chaînes. Elles ont une représentation littérale, peuvent être stockées dans des variables, peuvent être passées en tant qu'arguments, etc.
Récursivité. Lisp a été le premier langage de programmation à la prendre en charge.
Typage dynamique. En Lisp, toutes les variables sont en réalité des pointeurs. Les valeurs sont ce qui possède un type, pas des variables, et l'affectation ou la liaison de variables signifie copier des pointeurs, pas ce vers quoi ils pointent.
Collecte des ordures ménagères.
Programmes composés d'expressions. Les programmes Lisp sont des arbres d'expressions, chacune d'elles renvoyant une valeur. Cela contraste avec Fortran et la plupart des langages qui lui succèdent, qui font la distinction entre expressions et instructions.
Il était naturel d'avoir cette distinction dans Fortran I car il était impossible d'imbriquer des instructions. Ainsi, même si des expressions étaient nécessaires pour que les mathématiques fonctionnent, il n'y avait aucun intérêt à faire en sorte que quoi que ce soit d'autre renvoie une valeur, car rien ne pouvait l'attendre.
Cette limitation a disparu avec l'arrivée des langages structurés en blocs, mais il était alors trop tard. La distinction entre expressions et instructions était bien ancrée. Elle s'est propagée de Fortran à Algol, puis à leurs deux descendants.
Un type de symbole. Les symboles sont en fait des pointeurs vers des chaînes stockées dans une table de hachage. Vous pouvez donc tester l'égalité en comparant un pointeur, au lieu de comparer chaque caractère.
Une notation pour le code utilisant des arbres de symboles et de constantes.
Le langage entier est présent en permanence. Il n'y a pas de réelle distinction entre la lecture, la compilation et l'exécution. Vous pouvez compiler ou exécuter du code pendant la lecture, lire ou exécuter du code pendant la compilation et lire ou compiler du code pendant l'exécution.
L'exécution de code au moment de la lecture permet aux utilisateurs de reprogrammer la syntaxe de Lisp ; l'exécution de code au moment de la compilation est la base des macros ; la compilation au moment de l'exécution est la base de l'utilisation de Lisp comme langage d'extension dans des programmes comme Emacs ; et la lecture au moment de l'exécution permet aux programmes de communiquer en utilisant des s-expressions, une idée récemment réinventée sous le nom de XML.
Lorsque Lisp est apparu pour la première fois, ces idées étaient très éloignées de la pratique de programmation ordinaire, qui était largement dictée par le matériel disponible à la fin des années 1950. Au fil du temps, le langage par défaut, incarné dans une succession de langages populaires, a progressivement évolué vers Lisp. Les idées 1 à 5 sont désormais répandues. La numéro 6 commence à apparaître dans le courant dominant. Python a une forme de 7, bien qu'il ne semble pas y avoir de syntaxe pour cela.
Quant au numéro 8, il est peut-être le plus intéressant de tous. Les idées 8 et 9 ne sont entrées dans Lisp que par accident, parce que Steve Russell a implémenté quelque chose que McCarthy n'avait jamais eu l'intention d'implémenter. Et pourtant, ces idées se révèlent être responsables à la fois de l'apparence étrange de Lisp et de ses caractéristiques les plus distinctives. Lisp a l'air étrange non pas tant parce qu'il a une syntaxe étrange que parce qu'il n'a pas de syntaxe ; vous exprimez des programmes directement dans les arbres d'analyse qui sont construits en arrière-plan lorsque d'autres langages sont analysés, et ces arbres sont constitués de listes, qui sont des structures de données Lisp.
L'expression du langage dans ses propres structures de données s'avère être une fonctionnalité très puissante. Les idées 8 et 9 combinées signifient que vous pouvez écrire des programmes qui écrivent des programmes. Cela peut sembler une idée bizarre, mais c'est une chose courante en Lisp. La façon la plus courante de le faire est d'utiliser ce qu'on appelle une macro.
Le terme « macro » n'a pas la même signification en Lisp que dans d'autres langages. Une macro Lisp peut être n'importe quoi, d'une abréviation à un compilateur pour un nouveau langage. Si vous voulez vraiment comprendre Lisp, ou simplement élargir vos horizons de programmation, je vous conseille d'en apprendre davantage sur les macros.
Les macros (au sens de Lisp) sont, autant que je sache, spécifiques à Lisp. Cela tient en partie au fait que pour avoir des macros, il faut probablement rendre votre langage aussi étrange que Lisp. Cela tient peut-être aussi au fait que si vous ajoutez ce dernier incrément de puissance, vous ne pourrez plus prétendre avoir inventé un nouveau langage, mais seulement un nouveau dialecte de Lisp.
Je mentionne cela surtout pour plaisanter, mais c'est tout à fait vrai. Si vous définissez un langage qui possède car, cdr, cons, quote, cond, atom, eq et une notation pour les fonctions exprimées sous forme de listes, alors vous pouvez construire tout le reste de Lisp à partir de cela. C'est en fait la qualité qui définit Lisp : c'est pour y parvenir que McCarthy a donné à Lisp la forme qu'il a aujourd'hui.
Là où les langues comptent
Supposons donc que Lisp représente une sorte de limite que les langages courants s'approchent asymptotiquement. Cela signifie-t-il que vous devriez l'utiliser pour écrire des logiciels ? Combien perdez-vous en utilisant un langage moins puissant ? N'est-il pas parfois plus sage de ne pas être à la pointe de l'innovation ? Et la popularité n'est-elle pas dans une certaine mesure sa propre justification ? Le patron aux cheveux pointus n'a-t-il pas raison, par exemple, de vouloir utiliser un langage pour lequel il peut facilement embaucher des programmeurs ?
Il existe bien sûr des projets où le choix du langage de programmation n'a pas beaucoup d'importance. En règle générale, plus l'application est exigeante, plus vous avez de chances d'utiliser un langage puissant. Mais de nombreux projets ne sont pas exigeants du tout. La plupart des programmes consistent probablement à écrire de petits programmes de liaison, et pour ces derniers, vous pouvez utiliser n'importe quel langage que vous connaissez déjà et qui dispose de bonnes bibliothèques pour tout ce que vous devez faire. Si vous avez simplement besoin de transmettre des données d'une application Windows à une autre, utilisez bien sûr Visual Basic.
Vous pouvez aussi écrire de petits programmes de liaison en Lisp (je l'utilise comme calculatrice de bureau), mais le plus grand avantage des langages comme Lisp se situe à l'autre extrémité du spectre, où vous devez écrire des programmes sophistiqués pour résoudre des problèmes difficiles face à une concurrence féroce. Un bon exemple est le programme de recherche de tarifs aériens que ITA Software a concédé sous licence à Orbitz. Ces gars sont entrés sur un marché déjà dominé par deux gros concurrents bien établis, Travelocity et Expedia, et semblent les avoir humiliés sur le plan technologique.
Le cœur de l'application d'ITA est un programme Common Lisp de 200 000 lignes qui explore des possibilités bien plus nombreuses que celles de ses concurrents, qui utilisent apparemment encore les techniques de programmation de l'ère des mainframes. (Bien qu'ITA utilise aussi, dans un certain sens, un langage de programmation de l'ère des mainframes.) Je n'ai jamais vu aucun code d'ITA, mais selon l'un de leurs meilleurs hackers, ils utilisent beaucoup de macros, et je ne suis pas surpris de l'entendre.
Forces centripètes
Je ne dis pas que l'utilisation de technologies peu communes ne coûte rien. Le patron aux cheveux pointus n'a pas complètement tort de s'en inquiéter. Mais comme il ne comprend pas les risques, il a tendance à les amplifier.
Je pense à trois problèmes qui pourraient survenir en utilisant des langages moins courants. Vos programmes pourraient ne pas fonctionner correctement avec des programmes écrits dans d’autres langages. Vous pourriez avoir moins de bibliothèques à votre disposition. Et vous pourriez avoir du mal à embaucher des programmeurs.
Dans quelle mesure chacun de ces éléments constitue-t-il un problème ? L'importance du premier varie selon que vous avez ou non le contrôle sur l'ensemble du système. Si vous écrivez un logiciel qui doit s'exécuter sur la machine d'un utilisateur distant sur un système d'exploitation fermé et bogué (je ne cite aucun nom), il peut être avantageux d'écrire votre application dans le même langage que le système d'exploitation. Mais si vous contrôlez l'ensemble du système et que vous disposez du code source de toutes les parties, comme c'est probablement le cas d'ITA, vous pouvez utiliser les langages de votre choix. Si une incompatibilité survient, vous pouvez la résoudre vous-même.
Dans les applications basées sur serveur, on peut se permettre d'utiliser les technologies les plus avancées, et je pense que c'est la principale cause de ce que Jonathan Erickson appelle la « renaissance des langages de programmation ». C'est pourquoi nous entendons parler de nouveaux langages comme Perl et Python. Nous n'entendons pas parler de ces langages parce que les gens les utilisent pour écrire des applications Windows, mais parce qu'ils les utilisent sur des serveurs. Et à mesure que les logiciels quittent le bureau pour s'installer sur des serveurs (un avenir auquel même Microsoft semble résigné), la pression pour utiliser des technologies intermédiaires va diminuer.
Quant aux bibliothèques, leur importance dépend aussi de l'application. Pour des problèmes moins exigeants, la disponibilité des bibliothèques peut dépasser la puissance intrinsèque du langage. Où se situe le point d'équilibre ? Difficile à dire exactement, mais quel que soit le point, il manque tout ce que l'on pourrait appeler une application. Si une entreprise se considère comme une entreprise de logiciels et qu'elle écrit une application qui sera l'un de ses produits, elle fera probablement appel à plusieurs hackers et son écriture prendra au moins six mois. Dans un projet de cette taille, la puissance des langages commence probablement à l'emporter sur la commodité des bibliothèques préexistantes.
La troisième inquiétude du patron aux cheveux pointus, la difficulté d'embaucher des programmeurs, est à mon avis une fausse piste. Combien de hackers faut-il embaucher, après tout ? Nous savons tous maintenant que les logiciels sont mieux développés par des équipes de moins de dix personnes. Et vous ne devriez pas avoir de difficulté à embaucher autant de hackers pour n'importe quel langage dont vous avez entendu parler. Si vous ne parvenez pas à trouver dix hackers Lisp, votre entreprise est probablement basée dans la mauvaise ville pour développer des logiciels.
En fait, choisir un langage plus puissant diminue probablement la taille de l'équipe dont vous avez besoin, car (a) si vous utilisez un langage plus puissant, vous n'aurez probablement pas besoin d'autant de hackers, et (b) les hackers qui travaillent dans des langages plus avancés sont susceptibles d'être plus intelligents.
Je ne dis pas que vous ne subirez pas de fortes pressions pour utiliser ce qui est perçu comme des technologies « standards ». Chez Viaweb (maintenant Yahoo Store), nous avons fait froncer les sourcils parmi les investisseurs en capital-risque et les acquéreurs potentiels en utilisant Lisp. Mais nous avons également fait froncer les sourcils en utilisant des boîtiers Intel génériques comme serveurs au lieu de serveurs « de puissance industrielle » comme Sun, en utilisant une variante open source d'Unix alors obscure appelée FreeBSD au lieu d'un véritable système d'exploitation commercial comme Windows NT, en ignorant une prétendue norme de commerce électronique appelée SET dont personne ne se souvient aujourd'hui, etc.
Vous ne pouvez pas laisser les costumes prendre des décisions techniques à votre place. Est-ce que cela a alarmé certains acquéreurs potentiels que nous utilisions Lisp ? Certains, légèrement, mais si nous n'avions pas utilisé Lisp, nous n'aurions pas pu écrire le logiciel qui les a poussés à vouloir nous acheter. Ce qui leur semblait être une anomalie était en fait une relation de cause à effet.
Si vous lancez une start-up, ne concevez pas votre produit pour plaire aux capital-risqueurs ou aux acquéreurs potentiels. Concevez votre produit pour plaire aux utilisateurs. Si vous gagnez les utilisateurs, tout le reste suivra. Et si vous n'y parvenez pas, personne ne se souciera de l'orthodoxie réconfortante de vos choix technologiques.
Le prix de la moyenne
Combien perd-on en utilisant un langage moins puissant ? Il existe effectivement des données à ce sujet.
La mesure la plus pratique de la puissance est probablement la taille du code . L'objectif des langages de haut niveau est de vous fournir des abstractions plus grandes, des briques plus grosses, en quelque sorte, afin que vous n'ayez pas besoin d'autant de briques pour construire un mur d'une taille donnée. Ainsi, plus le langage est puissant, plus le programme est court (pas simplement en caractères, bien sûr, mais en éléments distincts).
Comment un langage plus puissant permet-il d'écrire des programmes plus courts ? Une technique que vous pouvez utiliser, si le langage vous le permet, est ce que l'on appelle la programmation ascendante . Au lieu d'écrire simplement votre application dans le langage de base, vous construisez sur le langage de base un langage pour écrire des programmes comme le vôtre, puis vous écrivez votre programme dans ce langage. Le code combiné peut être beaucoup plus court que si vous aviez écrit tout votre programme dans le langage de base - en fait, c'est ainsi que fonctionnent la plupart des algorithmes de compression. Un programme ascendant devrait également être plus facile à modifier, car dans de nombreux cas, la couche de langage n'aura pas à changer du tout.
La taille du code est importante, car le temps nécessaire à l'écriture d'un programme dépend principalement de sa longueur. Si votre programme est trois fois plus long dans un autre langage, il faudra trois fois plus de temps à écrire - et vous ne pouvez pas contourner ce problème en embauchant plus de personnel, car au-delà d'une certaine taille, les nouvelles recrues sont en fait une perte nette. Fred Brooks a décrit ce phénomène dans son célèbre livre The Mythical Man-Month, et tout ce que j'ai vu tend à confirmer ce qu'il dit.
Alors, de combien vos programmes sont-ils plus courts si vous les écrivez en Lisp ? La plupart des chiffres que j'ai entendus pour Lisp par rapport au C, par exemple, tournent autour de 7 à 10 fois. Mais un article récent sur ITA dans le magazine New Architect disait que « une ligne de Lisp peut remplacer 20 lignes de C », et comme cet article était rempli de citations du président de l'ITA, je suppose qu'ils ont obtenu ce chiffre de l'ITA. Si c'est le cas, alors nous pouvons y croire ; le logiciel de l'ITA comprend beaucoup de C et de C++ ainsi que de Lisp, donc ils parlent d'expérience.
Je suppose que ces multiples ne sont même pas constants. Je pense qu'ils augmentent lorsque vous êtes confronté à des problèmes plus difficiles et également lorsque vous avez des programmeurs plus intelligents. Un très bon hacker peut tirer davantage de meilleurs outils.
En tout cas, si vous deviez concurrencer l'ITA et choisir d'écrire votre logiciel en C, ils seraient capables de développer votre logiciel vingt fois plus vite que vous. Si vous passiez un an sur une nouvelle fonctionnalité, ils seraient capables de la dupliquer en moins de trois semaines. Alors que s'ils passaient seulement trois mois à développer quelque chose de nouveau, il leur faudrait cinq ans avant de l'avoir également.
Et vous savez quoi ? C'est le meilleur scénario possible. Lorsque vous parlez de ratios entre la taille du code et la taille du programme, vous supposez implicitement que vous pouvez réellement écrire le programme dans le langage le plus faible. Mais en fait, il existe des limites à ce que les programmeurs peuvent faire. Si vous essayez de résoudre un problème difficile avec un langage de trop bas niveau, vous atteignez un point où il y a tout simplement trop de choses à garder en tête en même temps.
Donc, quand je dis qu'il faudrait cinq ans au concurrent imaginaire d'ITA pour dupliquer quelque chose qu'ITA pourrait écrire en Lisp en trois mois, je veux dire cinq ans si tout se passe bien. En fait, étant donné la façon dont les choses fonctionnent dans la plupart des entreprises, tout projet de développement qui prendrait cinq ans ne sera probablement jamais terminé.
Je reconnais que c'est un cas extrême. Les hackers de l'ITA semblent être exceptionnellement intelligents, et le C est un langage de bas niveau. Mais dans un marché concurrentiel, même un différentiel de deux ou trois contre un suffirait à garantir que vous serez toujours à la traîne.
Une recette
C'est le genre de possibilité à laquelle le patron aux cheveux pointus ne veut même pas penser. Et donc la plupart d'entre eux ne le font pas. Parce que, vous savez, en fin de compte, le patron aux cheveux pointus ne se soucie pas de savoir si son entreprise se fait botter le derrière, tant que personne ne peut prouver que c'est de sa faute. Le plan le plus sûr pour lui personnellement est de rester proche du centre du troupeau.
Dans les grandes entreprises, l'expression utilisée pour décrire cette approche est « la meilleure pratique du secteur ». Son but est de protéger le patron aux cheveux pointus de toute responsabilité : s'il choisit quelque chose qui est « la meilleure pratique du secteur » et que l'entreprise perd, il ne peut pas être blâmé. Ce n'est pas lui qui a choisi, c'est le secteur qui l'a fait.
Je crois que ce terme était à l'origine utilisé pour décrire les méthodes comptables, etc. Cela signifie, grosso modo, qu'il ne faut rien faire de bizarre. Et en comptabilité, c'est probablement une bonne idée. Les termes « de pointe » et « comptabilité » ne sonnent pas bien ensemble. Mais lorsque vous importez ce critère dans les décisions relatives à la technologie, vous commencez à obtenir de mauvaises réponses.
La technologie doit souvent être à la pointe du progrès. Dans les langages de programmation, comme l'a souligné Erann Gat, ce que l'on obtient en réalité avec les « meilleures pratiques du secteur » n'est pas le meilleur, mais simplement la moyenne. Lorsqu'une décision vous oblige à développer des logiciels à une fraction du rythme de vos concurrents plus agressifs, la « meilleure pratique » est une appellation erronée.
Nous avons donc ici deux informations qui me semblent très précieuses. En fait, je le sais par expérience. Premièrement, les langues n'ont pas toutes la même puissance. Deuxièmement, la plupart des managers ignorent délibérément ce fait. Ces deux faits réunis sont littéralement une recette pour gagner de l'argent. L'ITA est un exemple de cette recette en action. Si vous voulez gagner dans le secteur des logiciels, attaquez-vous au problème le plus difficile que vous puissiez trouver, utilisez le langage le plus puissant que vous puissiez trouver et attendez que les patrons aux cheveux pointus de vos concurrents reviennent à la moyenne.
Annexe : Alimentation
Pour illustrer ce que je veux dire à propos de la puissance relative des langages de programmation, considérons le problème suivant. Nous voulons écrire une fonction qui génère des accumulateurs, c'est-à-dire une fonction qui prend un nombre n et renvoie une fonction qui prend un autre nombre i et renvoie n incrémenté de i.
(Cela augmente de , pas de plus. Un accumulateur doit accumuler.)
En Common Lisp, cela serait
(defun foo (n) (lambda (i) (incf ni)))
et en Perl 5,
sub foo { my ($n) = @_; sub {$n += shift} }
qui a plus d'éléments que la version Lisp car il faut extraire les paramètres manuellement en Perl.
En Smalltalk, le code est légèrement plus long qu'en Lisp
foo: n |s| s := n. ^[:i| s := s+i. ]
car même si en général les variables lexicales fonctionnent, on ne peut pas faire d'affectation à un paramètre, il faut donc créer une nouvelle variable s.
En Javascript, l'exemple est, encore une fois, légèrement plus long, car Javascript conserve la distinction entre les instructions et les expressions, vous avez donc besoin d'instructions de retour explicites pour renvoyer des valeurs :
function foo(n) { return function (i) { return n += i } }
(Pour être juste, Perl conserve également cette distinction, mais la traite de manière typique de Perl en vous permettant d'omettre les retours.)
Si vous essayez de traduire le code Lisp/Perl/Smalltalk/Javascript en Python, vous vous heurtez à certaines limitations. Comme Python ne prend pas entièrement en charge les variables lexicales, vous devez créer une structure de données pour contenir la valeur de n. Et bien que Python dispose d'un type de données de fonction, il n'existe pas de représentation littérale pour celui-ci (à moins que le corps ne soit qu'une seule expression), vous devez donc créer une fonction nommée à renvoyer. Voici ce que vous obtenez :
def foo(n): s = [n] def bar(i): s[0] += i return s[0] return bar
Les utilisateurs de Python pourraient légitimement se demander pourquoi ils ne peuvent pas simplement écrire
def foo(n): return lambda i: return n += i
ou même
def foo(n): lambda i: n += i
et je suppose qu'ils le feront probablement, un jour. (Mais s'ils ne veulent pas attendre que Python évolue jusqu'à Lisp, ils pourraient toujours simplement...)
Dans les langages OO, vous pouvez, dans une certaine mesure, simuler une fermeture (une fonction qui fait référence à des variables définies dans des portées englobantes) en définissant une classe avec une méthode et un champ pour remplacer chaque variable d'une portée englobante. Cela oblige le programmeur à effectuer le type d'analyse de code qui serait effectué par le compilateur dans un langage avec un support complet de la portée lexicale, et cela ne fonctionnera pas si plusieurs fonctions font référence à la même variable, mais c'est suffisant dans des cas simples comme celui-ci.
Les experts Python semblent convenir qu'il s'agit de la méthode préférée pour résoudre le problème en Python, en écrivant soit
def foo(n): class acc: def __init__(self, s): self.s = s def inc(self, i): self.s += i return self.s return acc(n).inc
ou
class foo: def __init__(self, n): self.n = n def __call__(self, i): self.n += i return self.n
Je les ai inclus parce que je ne voudrais pas que les défenseurs de Python disent que je déforme le langage, mais les deux me semblent plus complexes que la première version. Vous faites la même chose, en créant un emplacement séparé pour contenir l'accumulateur ; c'est juste un champ dans un objet au lieu de la tête d'une liste. Et l'utilisation de ces noms de champs spéciaux et réservés, en particulier call , semble un peu bidouille.
Dans la rivalité entre Perl et Python, la prétention des hackers de Python semble être que Python est une alternative plus élégante à Perl, mais ce que ce cas montre, c'est que la puissance est l'élégance ultime : le programme Perl est plus simple (a moins d'éléments), même si la syntaxe est un peu plus moche.
Qu'en est-il des autres langages ? Dans les autres langages mentionnés dans cette conférence (Fortran, C, C++, Java et Visual Basic), il n'est pas évident de pouvoir réellement résoudre ce problème. Ken Anderson dit que le code suivant est à peu près aussi proche que possible de Java :
public interface Inttoint { public int call(int i); }
public static Inttoint foo(final int n) { return new Inttoint() { int s = n; public int call(int i) { s = s + i; return s; }}; }
Cela ne correspond pas à la spécification car cela ne fonctionne que pour les entiers. Après de nombreux échanges de courrier électronique avec des hackers Java, je dirais qu'écrire une version correctement polymorphe qui se comporte comme les exemples précédents est quelque part entre sacrément maladroit et impossible. Si quelqu'un veut en écrire une, je serais très curieux de la voir, mais personnellement, j'ai dépassé le délai imparti.
Bien entendu, il n'est pas tout à fait vrai que ce problème ne peut pas être résolu dans d'autres langages. Le fait que tous ces langages soient équivalents à Turing signifie que, strictement parlant, vous pouvez écrire n'importe quel programme dans n'importe lequel d'entre eux. Alors, comment le feriez-vous ? Dans le cas extrême, en écrivant un interpréteur Lisp dans le langage le moins puissant.
Cela ressemble à une blague, mais cela arrive si souvent à des degrés divers dans les grands projets de programmation qu'il existe un nom pour ce phénomène, la dixième règle de Greenspun :
Tout programme C ou Fortran suffisamment compliqué contient une implémentation lente et informelle de la moitié de Common Lisp, spécifiée par ad hoc et pleine de bogues.
Si vous essayez de résoudre un problème difficile, la question n'est pas de savoir si vous utiliserez un langage suffisamment puissant, mais si vous allez (a) utiliser un langage puissant, (b) écrire un interpréteur de facto pour un tel langage, ou (c) devenir vous-même un compilateur humain pour un tel langage. Nous voyons déjà ce phénomène se produire dans l'exemple de Python, où nous simulons en fait le code qu'un compilateur générerait pour implémenter une variable lexicale.
Cette pratique est non seulement courante, mais institutionnalisée. Par exemple, dans le monde OO, on entend beaucoup parler de « patterns ». Je me demande si ces patterns ne sont pas parfois la preuve que c'est le compilateur humain qui est à l'œuvre. Quand je vois des patterns dans mes programmes, je considère cela comme un signe de problème. La forme d'un programme ne doit refléter que le problème qu'il doit résoudre. Toute autre régularité dans le code est un signe, du moins pour moi, que j'utilise des abstractions qui ne sont pas assez puissantes – souvent que je génère à la main les extensions d'une macro que je dois écrire.
Remarques
Le processeur IBM 704 avait à peu près la taille d'un réfrigérateur, mais il était beaucoup plus lourd. Le processeur pesait 1470 kg et les 4 Ko de RAM se trouvaient dans un boîtier séparé pesant 1800 kg supplémentaires. Le Sub-Zero 690, l'un des plus grands réfrigérateurs domestiques, pèse 308 kg.
Steve Russell a également écrit le premier jeu informatique (numérique), Spacewar, en 1962.
Si vous voulez tromper un patron aux cheveux pointus pour qu'il vous laisse écrire un logiciel en Lisp, vous pouvez essayer de lui dire que c'est du XML.
Voici le générateur d'accumulateur dans d'autres dialectes Lisp :
Scheme: (define (foo n) (lambda (i) (set! n (+ ni)) n)) Goo: (df foo (n) (op incf n _))) Arc: (def foo (n) [++ n _])
La triste histoire d’Erann Gat sur les « meilleures pratiques de l’industrie » au JPL m’a inspiré à aborder cette expression généralement mal appliquée.
Peter Norvig a découvert que 16 des 23 modèles dans Design Patterns étaient « invisibles ou plus simples » dans Lisp.
Je remercie les nombreuses personnes qui ont répondu à mes questions sur les différentes langues et/ou qui ont lu les brouillons de cet article, notamment Ken Anderson, Trevor Blackwell, Erann Gat, Dan Giffin, Sarah Harlin, Jeremy Hylton, Robert Morris, Peter Norvig, Guy Steele et Anton van Straaten. Ils ne sont pas responsables des opinions exprimées.
En rapport:
De nombreuses personnes ont répondu à cette discussion, j'ai donc créé une page supplémentaire pour traiter les problèmes qu'ils ont soulevés : Re : La revanche des Nerds .
Cela a également déclenché une discussion approfondie et souvent utile sur la liste de diffusion LL1 . Voir en particulier le courrier d'Anton van Straaten sur la compression sémantique.
Certains courriers sur LL1 m'ont amené à essayer d'approfondir le sujet du pouvoir du langage dans Succinctness is Power .
Un ensemble plus large d'implémentations canoniques du benchmark du générateur d'accumulateurs est rassemblé sur sa propre page.
Traduction japonaise ,traduction espagnole , traduction chinoise