LA REVANCHE DES NERDS
OriginalMai 2002
"Nous étions à la recherche des programmeurs C++. Nous avons réussi à en traîner beaucoup d'entre eux à peu près à 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 à tête pointue et une autre force tout aussi redoutable, les patrons à cheveux pointus. Tout le monde sait qui est le patron à 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 aussi la personne réelle dans leur entreprise sur laquelle il est basé.
Le patron à cheveux pointus combine miraculeusement deux qualités qui sont communes par elles-mêmes, mais rarement vues ensemble : (a) il ne sait absolument rien sur la technologie, et (b) il a des opinions très fortes à ce sujet.
Supposons, par exemple, que vous devez écrire un logiciel. Le patron à cheveux pointus n'a aucune idée de la façon dont ce logiciel doit fonctionner, et ne peut pas faire la différence entre un langage de programmation et un autre, et pourtant il sait dans quel langage vous devriez l'écrire. Exactement. Il pense que vous devriez l'écrire en Java.
Pourquoi pense-t-il cela ? Jetons un coup d'œil à l'intérieur du cerveau du patron à cheveux pointus. Ce qu'il pense ressemble à ceci. Java est un standard. Je sais que ça doit l'être, parce que j'en lis tout le temps dans la presse. Puisque c'est un standard, je ne vais pas avoir de problèmes en l'utilisant. Et cela signifie aussi qu'il y aura toujours beaucoup de programmeurs Java, donc si les programmeurs qui travaillent pour moi maintenant quittent, comme les programmeurs qui travaillent pour moi le font mystérieusement toujours, je peux facilement les remplacer.
Eh bien, cela ne semble pas si déraisonnable. Mais tout cela repose sur une hypothèse tacite, et cette hypothèse s'avère être fausse. Le patron à cheveux pointus croit que tous les langages de programmation sont à peu près équivalents. Si c'était vrai, il aurait raison sur toute la ligne. Si les langages sont tous équivalents, bien sûr, utilisez le langage que tout le monde utilise.
Mais tous les langages ne sont pas équivalents, et je pense que je peux vous le prouver sans même entrer dans les différences entre eux. Si vous aviez demandé au patron à cheveux pointus en 1992 dans quel langage le logiciel devait être écrit, il aurait répondu avec aussi peu d'hésitation qu'il le fait aujourd'hui. Le logiciel devrait être écrit en C++. Mais si les langages sont tous équivalents, pourquoi l'opinion du patron à cheveux pointus devrait-elle jamais changer ? En fait, pourquoi les développeurs de Java auraient-ils même pris la peine de créer un nouveau langage ?
On peut supposer que si vous créez un nouveau langage, c'est parce que vous pensez qu'il est meilleur d'une certaine manière que ce que les gens avaient déjà. Et en fait, Gosling précise dans le premier livre blanc sur Java que Java a été conçu pour corriger certains problèmes avec C++. Donc voilà : les langages ne sont pas tous équivalents. Si vous suivez le fil à travers le cerveau du patron à cheveux pointus jusqu'à Java, puis à travers l'histoire de Java jusqu'à ses origines, vous finissez par tenir une idée qui contredit l'hypothèse avec laquelle vous avez commencé.
Alors, qui a raison ? James Gosling ou le patron à cheveux pointus ? Sans surprise, Gosling a raison. Certains langages sont meilleurs, pour certains problèmes, que d'autres. Et vous savez, cela soulève des questions intéressantes. Java a été conçu pour être meilleur, pour certains problèmes, que C++. Quels problèmes ? Quand Java est-il meilleur et quand C++ ? Existe-t-il des situations où d'autres langages sont meilleurs que l'un ou l'autre ?
Une fois que vous commencez à considérer cette question, vous avez ouvert une véritable boîte de Pandore. Si le patron à cheveux pointus devait réfléchir au problème dans toute sa complexité, cela ferait exploser son cerveau. Tant qu'il considère tous les langages comme équivalents, tout ce qu'il a à faire est de choisir celui qui semble avoir le plus d'élan, et puisque c'est plus une question de mode que de technologie, même lui peut probablement obtenir la bonne réponse. Mais si les langages varient, il doit soudainement résoudre deux équations simultanées, essayant de trouver un équilibre optimal entre deux choses qu'il ne connaît pas : la pertinence relative des vingt ou plus de langages principaux pour le 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 à 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 beaucoup votre 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 langage de programmation cool et nouveau. 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 publicitaire clignotant payé par Sun.) Mais si vous regardez ce monde de près, vous constatez qu'il existe des degrés de coolness. Au sein de la sous-culture hacker, 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 des Java Server Pages. Mais il existe un autre langage, plus récent, appelé Python, dont les utilisateurs ont tendance à mépriser Perl, et plus attendent dans les coulisses.
Si vous regardez ces langages dans l'ordre, Java, Perl, Python, vous remarquez un schéma intéressant. Du moins, vous remarquez ce schéma si vous êtes un hacker Lisp. Chacun est progressivement plus semblable à 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é 1958.
Rattraper les mathématiques
Ce que je veux dire, c'est que Lisp a été découvert pour la première fois par John McCarthy en 1958, et que les langages de programmation populaires ne rattrapent que maintenant les idées qu'il a développées à l'époque.
Maintenant, comment cela pourrait-il être vrai ? La technologie informatique ne change-t-elle pas très rapidement ? Je veux dire, en 1958, les ordinateurs étaient des monstres 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, 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 dans le sens que nous entendons aujourd'hui. Ce que nous entendons par un langage de programmation est quelque chose que nous utilisons pour dire à un ordinateur quoi faire. McCarthy avait finalement l'intention de développer un langage de programmation dans ce sens, mais le Lisp que nous avons réellement obtenu était basé sur quelque chose de séparé qu'il a fait comme un 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 est plus concise et plus compréhensible que la description d'une machine de Turing universelle. C'était la fonction Lisp eval..., qui calcule la valeur d'une expression Lisp.... Écrire eval nécessitait d'inventer une notation représentant les fonctions Lisp en tant que 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 en pratique.
Ce qui s'est passé ensuite, c'est qu'à un moment donné à la fin de 1958, Steve Russell, l'un des étudiants diplômés de McCarthy, a regardé cette définition de eval et a réalisé que s'il la traduisait en langage machine, le résultat serait un interpréteur Lisp.
C'était une grande surprise à l'époque. Voici ce que McCarthy a dit à ce sujet plus tard dans une interview :
Steve Russell a dit, regardez, pourquoi ne programmerais-je pas ce eval..., et je lui ai dit, ho, ho, vous confondez théorie et pratique, ce eval est destiné à la lecture, pas au calcul. Mais il a continué et l'a fait. C'est-à-dire qu'il a compilé le eval de mon article en code machine [IBM] 704, corrigeant des bogues, puis a annoncé cela comme 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 l'espace de quelques semaines, je pense, McCarthy a découvert que son exercice théorique s'était transformé en un véritable langage de programmation - et un langage plus puissant que celui qu'il avait prévu.
Donc, la brève explication de pourquoi 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 se périment pas. La bonne chose à comparer à Lisp n'est pas le matériel des années 1950, mais, disons, l'algorithme Quicksort, qui a été découvert en 1960 et est toujours le tri général le plus rapide.
Il existe un autre langage qui survit encore des années 1950, Fortran, et il représente l'approche opposée en matière de conception de langage. Lisp était une pièce de théorie qui a été transformée de manière inattendue en un langage de programmation. Fortran a été développé intentionnellement comme un langage de programmation, mais ce que nous considérerions maintenant comme un langage de très bas niveau.
Fortran I, le langage qui a été développé en 1956, était un animal très différent du Fortran d'aujourd'hui. Fortran I était à peu près 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 d'aujourd'hui est maintenant sans doute plus proche de Lisp que de Fortran I.
Lisp et Fortran étaient les troncs de deux arbres évolutifs séparés, l'un enraciné dans les mathématiques et l'autre enraciné dans l'architecture machine. Ces deux arbres ont convergé depuis. Lisp a commencé puissant, et au cours des vingt années suivantes, il est devenu rapide. Les langages dits mainstream ont commencé rapides, et au cours des quarante années suivantes, ils sont devenus progressivement plus puissants, jusqu'à ce que maintenant les plus avancés d'entre eux soient assez proches de Lisp. Près, mais il leur manque encore quelques éléments....
Ce qui a rendu Lisp différent
Lorsqu'il a été développé pour la première fois, Lisp incarnait neuf nouvelles idées. Certaines d'entre elles sont maintenant considérées comme acquises, d'autres ne sont vues que dans des langages plus avancés, et deux sont encore uniques à Lisp. Les neuf idées sont, dans l'ordre de leur adoption par le mainstream,
Conditions. Une condition est une construction if-then-else. Nous les considérons comme acquises 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. Dans 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 arguments, etc.
Récursion. Lisp a été le premier langage de programmation à la prendre en charge.
Typage dynamique. Dans Lisp, toutes les variables sont effectivement des pointeurs. Les valeurs ont des types, pas les variables, et assigner ou lier des variables signifie copier des pointeurs, pas ce à quoi ils pointent.
Collecte des ordures.
Des programmes composés d'expressions. Les programmes Lisp sont des arbres d'expressions, chacune d'elles retournant une valeur. Cela contraste avec Fortran et la plupart des langages suivants, qui distinguent entre expressions et instructions.
Il était naturel d'avoir cette distinction dans Fortran I parce que vous ne pouviez pas imbriquer des instructions. Et donc, bien que vous ayez besoin d'expressions pour que les mathématiques fonctionnent, il n'y avait aucun intérêt à faire en sorte que quoi que ce soit d'autre retourne une valeur, car il ne pouvait pas y avoir quoi que ce soit attendant cela.
Cette limitation a disparu avec l'arrivée des langages à structure de blocs, mais à ce moment-là, il était trop tard. La distinction entre expressions et instructions était ancrée. Elle s'est répandue de Fortran à Algol, puis à leurs descendants.
Un type de symbole. Les symboles sont effectivement des pointeurs vers des chaînes stockées dans une table de hachage. Ainsi, vous pouvez 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 là tout le temps. Il n'y a pas de véritable distinction entre le temps de lecture, le temps de compilation et le temps d'exécution. Vous pouvez compiler ou exécuter du code tout en lisant, lire ou exécuter du code tout en compilant, et lire ou compiler du code à l'exécution.
Exécuter du code au moment de la lecture permet aux utilisateurs de reprogrammer la syntaxe de Lisp ; exécuter du code au moment de la compilation est la base des macros ; compiler à l'exécution est la base de l'utilisation de Lisp comme langage d'extension dans des programmes comme Emacs ; et lire à l'exécution permet aux programmes de communiquer en utilisant des s-expressions, une idée récemment réinventée sous forme 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 maintenant répandues. Le numéro 6 commence à apparaître dans le mainstream. Python a une forme de 7, bien qu'il ne semble pas y avoir de syntaxe pour cela.
Quant au numéro 8, cela pourrait être le plus intéressant de tous. Les idées 8 et 9 ne sont devenues partie intégrante de Lisp que par accident, parce que Steve Russell a implémenté quelque chose que McCarthy n'avait jamais prévu d'implémenter. Et pourtant, ces idées s'avèrent être responsables à la fois de l'apparence étrange de Lisp et de ses caractéristiques les plus distinctives. Lisp semble étrange non pas tant à cause de sa 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 coulisses lorsque d'autres langages sont analysés, et ces arbres sont constitués de listes, qui sont des structures de données Lisp.
Exprimer le langage dans ses propres structures de données s'avère être une caractéristique très puissante. Les idées 8 et 9 ensemble signifient que vous pouvez écrire des programmes qui écrivent des programmes. Cela peut sembler une idée bizarre, mais c'est une chose courante dans Lisp. La façon la plus courante de le faire est avec quelque chose appelé une macro.
Le terme "macro" ne signifie pas en Lisp ce qu'il signifie dans d'autres langages. Une macro Lisp peut être tout, 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 recommande d'en apprendre davantage sur les macros.
Les macros (dans le sens Lisp) sont encore, autant que je sache, uniques à Lisp. Cela est en partie dû au fait que pour avoir des macros, vous devez probablement faire en sorte que votre langage ait l'air aussi étrange que Lisp. Cela peut également être parce que si vous ajoutez ce dernier incrément de puissance, vous ne pouvez plus prétendre avoir inventé un nouveau langage, mais seulement un nouveau dialecte de Lisp.
Je mentionne cela principalement comme une blague, mais c'est tout à fait vrai. Si vous définissez un langage qui a 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é définissante de Lisp : c'est pour rendre cela possible que McCarthy a donné à Lisp la forme qu'il a.
Où les langages comptent
Alors supposons que Lisp représente une sorte de limite que les langages mainstream approchent asymptotiquement - cela signifie-t-il que vous devriez réellement l'utiliser pour écrire des logiciels ? Combien perdez-vous en utilisant un langage moins puissant ? N'est-il pas plus sage, parfois, 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 à cheveux pointus n'a-t-il pas raison, par exemple, de vouloir utiliser un langage pour lequel il peut facilement embaucher des programmeurs ?
Il y a, 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 tirez de levier en utilisant un langage puissant. Mais de nombreux projets ne sont pas du tout exigeants. La plupart de la programmation consiste probablement à écrire de petits programmes de liaison, et pour de petits programmes de liaison, vous pouvez utiliser n'importe quel langage que vous connaissez déjà et qui a de bonnes bibliothèques pour ce que vous devez faire. Si vous devez simplement transférer des données d'une application Windows à une autre, bien sûr, utilisez Visual Basic.
Vous pouvez également é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 qu'ITA Software licence à Orbitz. Ces gars-là sont entrés sur un marché déjà dominé par deux grands concurrents bien établis, Travelocity et Expedia, et semblent les avoir technologiquement humiliés.
Le cœur de l'application d'ITA est un programme Common Lisp de 200 000 lignes qui recherche de nombreuses ordres de grandeur de possibilités de plus que leurs concurrents, qui apparemment utilisent encore des techniques de programmation de l'ère des mainframes. (Bien qu'ITA utilise également en un sens un langage de programmation de l'ère des mainframes.) Je n'ai jamais vu de 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 qu'il n'y a pas de coût à utiliser des technologies peu courantes. Le patron à cheveux pointus n'a pas complètement tort de s'inquiéter à ce sujet. Mais parce qu'il ne comprend pas les risques, il a tendance à les amplifier.
Je peux penser à trois problèmes qui pourraient découler de l'utilisation de langages moins courants. Vos programmes pourraient ne pas bien fonctionner avec des programmes écrits dans d'autres langages. Vous pourriez avoir moins de bibliothèques à votre disposition. Et vous pourriez avoir des difficultés à embaucher des programmeurs.
À quel point chacun de ces problèmes est-il important ? L'importance du premier varie selon que vous avez le contrôle sur l'ensemble du système. Si vous écrivez un logiciel qui doit fonctionner sur la machine d'un utilisateur distant sur un système d'exploitation fermé et bogué (je ne mentionne pas de noms), il peut y avoir des avantages à é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 avez le code source de toutes les parties, comme ITA le fait probablement, vous pouvez utiliser n'importe quel langage que vous voulez. Si une incompatibilité survient, vous pouvez la corriger vous-même.
Dans les applications basées sur des serveurs, vous pouvez vous en sortir en utilisant 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 même 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 que les gens les utilisent sur des serveurs. Et à mesure que les logiciels se déplacent hors du bureau et sur des serveurs (un avenir auquel même Microsoft semble résigné), il y aura de moins en moins de pression pour utiliser des technologies de milieu de gamme.
Quant aux bibliothèques, leur importance dépend également de l'application. Pour des problèmes moins exigeants, la disponibilité de bibliothèques peut l'emporter sur la puissance intrinsèque du langage. Où se trouve le point d'équilibre ? Difficile à dire exactement, mais où qu'il soit, il est en deçà de tout ce que vous pourriez appeler une application. Si une entreprise se considère comme étant dans le secteur des logiciels, et qu'elle écrit une application qui sera l'un de ses produits, alors cela impliquera probablement plusieurs hackers et prendra au moins six mois à écrire. Dans un projet de cette taille, les langages puissants commencent probablement à l'emporter sur la commodité des bibliothèques préexistantes.
La troisième préoccupation du patron à cheveux pointus, la difficulté d'embaucher des programmeurs, je pense que c'est un faux problème. Combien de hackers devez-vous embaucher, après tout ? Sûrement, à ce stade, nous savons tous que les logiciels se développent mieux par des équipes de moins de dix personnes. Et vous ne devriez pas avoir de mal à embaucher des hackers à cette échelle pour n'importe quel langage dont quelqu'un a déjà entendu parler. Si vous ne pouvez pas trouver dix hackers Lisp, alors 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 de tant 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 beaucoup de pression pour utiliser ce qui est perçu comme des technologies "standards". Chez Viaweb (maintenant Yahoo Store), nous avons suscité quelques sourcils parmi les investisseurs en capital-risque et les acquéreurs potentiels en utilisant Lisp. Mais nous avons également suscité des sourcils en utilisant des boîtiers Intel génériques comme serveurs au lieu de serveurs "industriels" comme les Suns, en utilisant une variante Unix open-source alors obscure appelée FreeBSD au lieu d'un véritable système d'exploitation commercial comme Windows NT, en ignorant un prétendu standard de commerce électronique appelé SET dont personne ne se souvient maintenant, et ainsi de suite.
Vous ne pouvez pas laisser les costumes prendre des décisions techniques pour vous. Cela a-t-il 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 fait vouloir nous acheter. Ce qui leur semblait une anomalie était en fait une relation de cause à effet.
Si vous démarrez une startup, ne concevez pas votre produit pour plaire aux investisseurs en capital-risque ou aux acquéreurs potentiels. Concevez votre produit pour plaire aux utilisateurs. Si vous gagnez les utilisateurs, tout le reste suivra. Et si vous ne le faites pas, personne ne se souciera de la manière dont vos choix technologiques étaient confortablement orthodoxes.
Le coût d'être moyen
Combien perdez-vous en utilisant un langage moins puissant ? Il existe en fait 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 donner de plus grandes abstractions - de plus grandes briques, pour ainsi dire, afin que vous n'ayez pas besoin d'en utiliser autant pour construire un mur d'une taille donnée. Plus le langage est puissant, plus le programme est court (non seulement en caractères, bien sûr, mais en éléments distincts).
Comment un langage plus puissant vous permet-il d'écrire des programmes plus courts ? Une technique que vous pouvez utiliser, si le langage vous le permet, est quelque chose appelé programmation ascendante. Au lieu d'écrire simplement votre application dans le langage de base, vous construisez au-dessus du langage de base un langage pour écrire des programmes comme le vôtre, puis écrivez votre programme dedans. Le code combiné peut être beaucoup plus court que si vous aviez écrit l'ensemble de votre programme dans le langage de base - en effet, 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 besoin de changer du tout.
La taille du code est importante, car le temps qu'il faut pour écrire un programme dépend principalement de sa longueur. Si votre programme serait trois fois plus long dans un autre langage, il prendra trois fois plus de temps à écrire - et vous ne pouvez pas contourner cela en embauchant plus de personnes, 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 a tendance à confirmer ce qu'il a dit.
Alors, à quel point vos programmes sont-ils plus courts si vous les écrivez en Lisp ? La plupart des chiffres que j'ai entendus pour Lisp par rapport à C, par exemple, ont été d'environ 7 à 10 fois. Mais un article récent sur ITA dans le magazine New Architect a déclaré que "une ligne de Lisp peut remplacer 20 lignes de C", et comme cet article était plein de citations du président d'ITA, je suppose qu'ils ont obtenu ce chiffre d'ITA. Si c'est le cas, alors nous pouvons lui faire confiance ; le logiciel d'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 aussi lorsque vous avez des programmeurs plus intelligents. Un hacker vraiment bon peut tirer plus de meilleurs outils.
Comme un point de données sur la courbe, en tout cas, si vous deviez rivaliser avec ITA et choisissiez d'écrire votre logiciel en C, ils seraient capables de développer un 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 juste trois mois à développer quelque chose de nouveau, il faudrait cinq ans avant que vous l'ayez aussi.
Et vous savez quoi ? C'est le meilleur des scénarios. Lorsque vous parlez de ratios de taille de code, vous supposez implicitement que vous pouvez réellement écrire le programme dans le langage le plus faible. Mais en fait, il y a des limites à ce que les programmeurs peuvent faire. Si vous essayez de résoudre un problème difficile avec un langage 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 rien ne va mal. En fait, la façon dont les choses fonctionnent dans la plupart des entreprises, tout projet de développement qui prendrait cinq ans est susceptible de ne jamais être terminé.
J'admets que c'est un cas extrême. Les hackers d'ITA semblent être exceptionnellement intelligents, et C est un langage assez bas niveau. Mais dans un marché concurrentiel, même un différentiel de deux ou trois à un serait suffisant pour garantir que vous seriez toujours à la traîne.
Une recette
C'est le genre de possibilité que le patron à cheveux pointus ne veut même pas envisager. Et donc, la plupart d'entre eux ne le font pas. Parce que, vous savez, quand il s'agit de cela, le patron à cheveux pointus ne se soucie pas si son entreprise se fait battre, 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.
Au sein des grandes organisations, la phrase utilisée pour décrire cette approche est "meilleure pratique de l'industrie". Son but est de protéger le patron à cheveux pointus de la responsabilité : s'il choisit quelque chose qui est "meilleure pratique de l'industrie", et que l'entreprise perd, il ne peut pas être blâmé. Ce n'est pas lui qui a choisi, c'est l'industrie qui l'a fait.
Je crois que ce terme a été utilisé à l'origine pour décrire des méthodes comptables et ainsi de suite. Ce que cela signifie, en gros, c'est ne faites rien de bizarre. Et en comptabilité, c'est probablement une bonne idée. Les termes "à la pointe" et "comptabilité" ne sonnent pas bien ensemble. Mais lorsque vous importez ce critère dans des décisions technologiques, vous commencez à obtenir de mauvaises réponses.
La technologie devrait souvent être à la pointe. Dans les langages de programmation, comme Erann Gat l'a souligné, ce que "meilleure pratique de l'industrie" vous apporte en réalité n'est pas le meilleur, mais simplement le moyen. Lorsqu'une décision vous amène à développer des logiciels à un rythme inférieur à celui de concurrents plus agressifs, "meilleure pratique" est un terme inapproprié.
Donc, ici, nous avons deux informations que je pense très précieuses. En fait, je le sais par ma propre expérience. Numéro 1, les langages varient en puissance. Numéro 2, la plupart des gestionnaires ignorent délibérément cela. Entre elles, ces deux réalités sont littéralement une recette pour gagner de l'argent. ITA est un exemple de cette recette en action. Si vous voulez gagner dans le secteur des logiciels, prenez simplement le problème le plus difficile que vous puissiez trouver, utilisez le langage le plus puissant que vous puissiez obtenir, et attendez que les patrons à cheveux pointus de vos concurrents reviennent à la moyenne.
Annexe : Puissance
Pour illustrer ce que je veux dire sur la puissance relative des langages de programmation, considérons le problème suivant. Nous voulons écrire une fonction qui génère des accumulateurs - une fonction qui prend un nombre n et retourne une fonction qui prend un autre nombre i et retourne n incrémenté par i.
(C'est incrémenté par, pas plus. Un accumulateur doit accumuler.)
En Common Lisp, cela serait
(defun foo (n)
(lambda (i) (incf n i)))
et en Perl 5,
sub foo {
my ($n) = @_;
sub {$n += shift}
}
qui a plus d'éléments que la version Lisp parce que vous devez 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 bien qu'en général les variables lexicales fonctionnent, vous ne pouvez pas faire d'assignation à un paramètre, donc vous devez 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 instructions et expressions, donc vous avez besoin d'instructions de retour explicites pour retourner 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 typiquement Perl en vous permettant d'omettre les retours.)
Si vous essayez de traduire le code Lisp/Perl/Smalltalk/Javascript en Python, vous rencontrez certaines limitations. Parce que 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 ait un type de données fonction, il n'y a pas de représentation littérale pour un (à moins que le corps ne soit qu'une seule expression), donc vous devez créer une fonction nommée à retourner. 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 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'à devenir Lisp, ils pourraient toujours juste...)
Dans les langages orientés objet, 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 à faire le genre d'analyse de code qui serait effectuée par le compilateur dans un langage avec un support complet pour la portée lexicale, et cela ne fonctionnera pas si plus d'une fonction fait référence à la même variable, mais c'est suffisant dans des cas simples comme celui-ci.
Les experts en Python semblent s'accorder à dire que c'est la manière préférée de 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
J'inclus cela 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 mettant en place un endroit séparé pour tenir 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 être un hack.
Dans la rivalité entre Perl et Python, la revendication des hackers Python semble être que Python est une alternative plus élégante à Perl, mais ce que ce cas montre, c'est que la puissance est la véritable élégance : le programme Perl est plus simple (a moins d'éléments), même si la syntaxe est un peu plus laide.
Qu'en est-il des autres langages ? Dans les autres langages mentionnés dans cette discussion - Fortran, C, C++, Java et Visual Basic - il n'est pas clair si vous pouvez réellement résoudre ce problème. Ken Anderson dit que le code suivant est à peu près aussi proche que vous pouvez l'obtenir en 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 respecte pas la spécification car cela ne fonctionne que pour les entiers. Après de nombreux échanges d'e-mails 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 extrêmement difficile et impossible. Si quelqu'un veut en écrire une, je serais très curieux de la voir, mais personnellement, j'ai dépassé le temps imparti.
Il n'est pas littéralement vrai que vous ne pouvez pas résoudre ce problème dans d'autres langages, bien sûr. Le fait que tous ces langages soient équivalents de Turing signifie qu'en termes stricts, vous pouvez écrire n'importe quel programme dans n'importe lequel d'eux. Alors, comment le feriez-vous ? Dans le cas limite, en écrivant un interpréteur Lisp dans le langage moins puissant.
Cela semble être une blague, mais cela se produit si souvent à des degrés divers dans de grands projets de programmation qu'il y a un nom pour le phénomène, la dixième règle de Greenspun :
Tout programme C ou Fortran suffisamment compliqué contient une implémentation ad hoc informellement spécifiée, boguée et lente de la moitié de Common Lisp.
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 (a) utiliserez un langage puissant, (b) écrirez un interpréteur de facto pour l'un, ou (c) deviendrez vous-même un compilateur humain pour l'un. Nous voyons déjà cela commencer à se produire dans l'exemple Python, où nous simulons en effet le code qu'un compilateur générerait pour implémenter une variable lexicale.
Cette pratique n'est pas seulement courante, mais institutionnalisée. Par exemple, dans le monde OO, vous entendez beaucoup parler de "modèles". Je me demande si ces modèles ne sont pas parfois la preuve du cas (c), le compilateur humain, à l'œuvre. Lorsque je vois des modèles dans mes programmes, je considère cela comme un signe de problème. La forme d'un programme devrait refléter uniquement le problème qu'il doit résoudre. Toute autre régularité dans le code est un signe, pour moi du moins, que j'utilise des abstractions qui ne sont pas assez puissantes - souvent que je génère à la main les expansions de certaines macros que je dois écrire.
Notes
Le CPU IBM 704 était à peu près de la taille d'un réfrigérateur, mais beaucoup plus lourd. Le CPU pesait 3150 livres, et les 4 Ko de RAM étaient dans une boîte séparée pesant encore 4000 livres. Le Sub-Zero 690, l'un des plus grands réfrigérateurs domestiques, pèse 656 livres.
Steve Russell a également écrit le premier jeu vidéo (numérique), Spacewar, en 1962.
Si vous voulez tromper un patron à cheveux pointus pour qu'il vous laisse écrire des logiciels en Lisp, vous pourriez essayer de lui dire que c'est XML.
Voici le générateur d'accumulateurs dans d'autres dialectes Lisp :
Scheme : (define (foo n)
(lambda (i) (set! n (+ n i)) n))
Goo : (df foo (n) (op incf n _)))
Arc : (def foo (n) [++ n _])
L'histoire triste d'Erann Gat sur la "meilleure pratique de l'industrie" à JPL m'a inspiré à aborder cette phrase 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" en Lisp.
Merci aux nombreuses personnes qui ont répondu à mes questions sur divers langages et/ou ont lu des brouillons de ceci, y compris Ken Anderson, Trevor Blackwell, Erann Gat, Dan Giffin, Sarah Harlin, Jeremy Hylton, Robert Morris, Peter Norvig, Guy Steele et Anton van Straaten. Ils ne portent aucune responsabilité pour les opinions exprimées.
Liens connexes :
De nombreuses personnes ont répondu à cette discussion, donc j'ai mis en place une page supplémentaire pour traiter les questions qu'elles ont soulevées : Re : La revanche des nerds.
Cela a également déclenché une discussion extensive et souvent utile sur la liste de diffusion LL1. Voir particulièrement le mail d'Anton van Straaten sur la compression sémantique.
Une partie du mail sur LL1 m'a amené à essayer d'approfondir le sujet de la puissance des langages dans La concision est puissance.
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