Des articles approfondis sur les technologies qui façonnent l'avenir.

Pourquoi Lisp compte encore après six décennies

Lisp a plus de 65 ans et influence toujours les langages modernes. Pourquoi il perdure et ce que les développeurs d'aujourd'hui peuvent en tirer.

Arbre ancien dont les branches sont formées de parenthèses imbriquées, portant des fruits lumineux

Tous les quelques années, quelqu'un écrit un essai intitulé « Lisp est mort », et tous les quelques années, quelqu'un d'autre fait remarquer que la moitié des fonctionnalités de son langage moderne préféré ont été empruntées à Lisp. Ramasse-miettes, fonctions de première classe, closures, typage dynamique, homoiconicité, développement piloté par le REPL, macros : tout cela a été inventé ou popularisé dans Lisp avant la naissance de la plupart des développeurs actuels. Lisp est le genre de projet qui demande des décennies pour être pleinement apprécié.

Et pourtant. Demandez à un développeur en activité s'il a déjà utilisé Lisp : la plupart répondront non. Demandez-lui s'il lancerait un projet de production en Lisp : la plupart vous regarderont d'un drôle d'air. Il y a là un paradoxe : les idées de Lisp ont conquis le monde de la programmation, mais Lisp lui-même reste un langage de niche. Comprendre pourquoi est plus intéressant que les discours « Lisp, c'est bien, les autres langages, non » qu'on trouve dans la plupart des plaidoyers en faveur de Lisp.

Ce que Lisp a vraiment bien fait

Lisp a été créé par John McCarthy en 1958. Pour replacer cela en perspective, FORTRAN venait d'avoir un an. COBOL n'existait pas encore. Le mini-ordinateur PDP-1 ne sortirait que deux ans plus tard. Lisp a été conçu sur papier puis implémenté sur un IBM 704, une machine qui remplissait une pièce et utilisait des mots de 36 bits.

Malgré cela, McCarthy a fait des choix de conception qui restent pertinents six décennies plus tard. Les plus importants :

Le code comme donnée (homoiconicité)

Dans la plupart des langages, le code et les données sont fondamentalement différents. Vous écrivez du code dans une syntaxe, et il manipule des structures de données. En Lisp, le code est une donnée. Un programme Lisp est constitué de listes. L'expression (+ 1 2) est à la fois un appel de fonction qui additionne 1 et 2, et une liste de trois éléments : le symbole +, le nombre 1 et le nombre 2. Vous pouvez manipuler cette liste (la réorganiser, y ajouter des éléments, la transformer), puis exécuter le résultat.

;; This is a function call
(+ 1 2)  ; => 3
;; This is a list containing the same elements
'(+ 1 2)  ; => (+ 1 2)
;; You can build code as data and then evaluate it
(def my-expr '(+ 1 2))
(eval my-expr)  ; => 3
;; Or transform it
(def doubled (list '* 2 my-expr))
;; doubled is now (* 2 (+ 1 2))
(eval doubled)  ; => 6

Cela semble anecdotique jusqu'à ce que l'on mesure ce que cela permet : des programmes qui écrivent des programmes. Les macros Lisp ne font pas de substitution de texte comme les macros du préprocesseur C. Elles reçoivent la structure réelle du programme sous forme de structure de données, la transforment avec toute la puissance du langage, puis renvoient une nouvelle structure de programme. C'est de la génération de code à la compilation, avec le compilateur lui-même comme API.

Le REPL comme environnement de développement

Lisp a inventé la boucle Lecture-Évaluation-Affichage (REPL) : l'idée que l'on doit pouvoir taper une expression, la voir évaluée immédiatement et observer le résultat. Cela paraît banal à une époque où chaque langage propose un REPL, mais celui de Lisp va plus loin que la plupart.

Avec Common Lisp ou Clojure, on ne se contente pas de tester des expressions isolées dans le REPL. On développe des systèmes entiers de manière interactive. On définit une fonction, on la teste, on la redéfinit, le tout pendant que le programme tourne. On peut inspecter et modifier l'état en cours d'exécution. On peut recompiler une seule fonction sans redémarrer l'application. Le cycle de développement n'est pas écrire-compiler-exécuter-déboguer : c'est une conversation continue avec un système vivant.

Les développeurs Clojure travaillent souvent de cette façon : ils connectent leur éditeur à un REPL en cours d'exécution, écrivent du code dans l'éditeur, évaluent des expressions individuelles d'une simple frappe de touche et voient les résultats immédiatement. La boucle de retour se mesure en millisecondes, pas en minutes. Une fois qu'on a travaillé ainsi, le cycle traditionnel édition-compilation-redémarrage ressemble à une correspondance par courrier.

Une syntaxe minimale, une flexibilité maximale

La syntaxe de Lisp, ou plutôt son absence de syntaxe, est sa caractéristique la plus clivante. Il n'existe pas de forme spéciale pour les instructions if, les boucles for ou les définitions de classes. Tout est une liste dont l'opérateur vient en premier : (if condition then-expr else-expr), (defn name [args] body), (for [x (range 10)] (* x x)). Les parenthèses qui rebutent les nouveaux venus sont le prix à payer pour le langage le plus régulier syntaxiquement jamais conçu.

Cette régularité a un avantage pratique : l'outillage. Quand chaque construction suit le même motif (opérateur opérandes...), les éditeurs peuvent indenter, refactoriser et naviguer dans le code de manière structurelle et fiable. « Sélectionner l'expression englobante » est sans ambiguïté en Lisp : c'est toujours la parenthèse correspondante. Dans les langages à syntaxe variée, l'édition structurelle reste un problème non résolu. En Lisp, il est résolu depuis les années 1960.

Pourquoi Lisp n'a pas gagné

Si Lisp est si bon, pourquoi tout le monde ne l'utilise-t-il pas ? La réponse habituelle des défenseurs de Lisp, « l'industrie a tort », n'est pas très utile et largement fausse. Lisp n'a pas atteint une adoption massive pour des raisons réelles et non triviales.

  • Le retard de l'écosystème. Les bibliothèques comptent davantage que les fonctionnalités du langage pour la plupart des travaux concrets. Python n'est pas populaire parce que sa syntaxe est belle, mais parce que pip install vous donne un accès immédiat à des milliers de paquets bien entretenus pour la data science, le développement web, le machine learning et tout le reste. Les écosystèmes Lisp (Common Lisp, Scheme, Clojure) sont solides mais plus restreints. Vous passerez plus de temps à tout écrire soi-même ou à adapter des bibliothèques moins bien entretenues.
  • La courbe d'apprentissage est concentrée au début. La syntaxe parenthésée de Lisp est d'une simplicité trompeuse une fois assimilée, mais elle constitue un vrai obstacle pour les débutants. Plus important encore, écrire du Lisp idiomatique demande de penser d'une manière peu familière si l'on vient de la programmation procédurale ou orientée objet. La récompense arrive plus tard, mais beaucoup de développeurs abandonnent avant d'y parvenir.
  • La fragmentation. « Lisp » n'est pas un langage unique, c'est une famille. Common Lisp, Scheme, Racket, Clojure, Emacs Lisp, Hy, Janet : chacun a ses forces, ses écosystèmes et ses communautés. Cette fragmentation empêche la concentration des efforts qui fait grandir les écosystèmes.
  • Le soutien d'une entreprise compte. Java a eu Sun, C# a eu Microsoft, Go a eu Google, Python a eu Google et une immense communauté de data science. Le langage le plus réussi de la famille Lisp en production, Clojure, a gagné du terrain en partie grâce à Rich Hickey, un penseur et communicant exceptionnellement clair, mais il lui manque toujours la poussée institutionnelle qui favorise l'adoption massive.

Clojure : un Lisp qui a tiré les leçons de l'histoire

Clojure mérite une attention particulière, car il montre comment reprendre les forces de Lisp et les rendre utilisables dans le développement logiciel moderne. Rich Hickey a conçu Clojure en 2007 en ayant pleinement conscience des raisons pour lesquelles les Lisp précédents n'avaient pas atteint une adoption massive, et il a fait des compromis délibérés.

  • Exécution sur la JVM. Plutôt que de construire un écosystème séparé à partir de zéro, Clojure tourne sur la machine virtuelle Java et peut utiliser directement n'importe quelle bibliothèque Java. Besoin d'un pilote de base de données ? Prenez celui de Java. Besoin d'un client HTTP ? Prenez celui de Java. Cette seule décision a donné à Clojure accès à des milliers de bibliothèques éprouvées dès le premier jour.
  • Immutabilité par défaut. Les structures de données de base de Clojure (listes, vecteurs, tables de hachage, ensembles) sont immuables. On ne modifie pas une map, on en crée une nouvelle avec les changements souhaités. Cela élimine des familles entières de bugs de concurrence et rend les programmes plus faciles à raisonner. Les structures de données persistantes sous le capot partagent leur structure pour rester efficaces.
  • Primitives de concurrence pragmatiques. Atoms, refs, agents : Clojure propose plusieurs modèles de concurrence, chacun adapté à des schémas différents. Ce n'était pas une élégance théorique, mais le résultat de l'expérience directe de Hickey avec les difficultés de l'état mutable partagé dans les applications Java en production.
  • ClojureScript. Clojure se compile en JavaScript, ce qui donne accès aux écosystèmes du navigateur et de Node.js. Écrivez votre backend en Clojure, votre frontend en ClojureScript, et partagez du code entre les deux. C'est la même promesse que TypeScript ou Kotlin Multiplatform, mais elle est arrivée plus tôt.

Ce que les langages modernes ont emprunté

Même si vous n'écrivez jamais une ligne de Lisp, vous utilisez ses idées au quotidien. Son influence est si diffuse qu'en suivre la trace revient à mesurer à quel point elle est profonde.

Les map, filter et reduce de JavaScript descendent directement des fonctions de traitement de listes de Lisp : le langage porte d'ailleurs ce nom (LISt Processing). Les compréhensions de listes de Python sont du sucre syntaxique pour les mêmes opérations. Le pattern matching de Rust remonte, via ML, aux expressions cond de Lisp. Les closures de Swift, la syntaxe des lambdas de Kotlin, l'API Streams de Java : tous ces concepts sont des idées de Lisp sous d'autres habits.

Les systèmes de macros de Rust (macro_rules! et les macros procédurales), d'Elixir, de Nim et de Julia s'inspirent directement des macros de Lisp, même si aucun n'atteint tout à fait la même fluidité, car aucun de ces langages n'a une syntaxe homoiconique. Quand votre code est une donnée, les macros deviennent triviales. Quand votre code a une syntaxe complexe, les macros doivent analyser et générer cette syntaxe, ce qui ajoute des frictions.

Le développement piloté par le REPL est devenu la norme en data science (les notebooks Jupyter sont essentiellement des REPL avec persistance), et des outils comme Figwheel et shadow-cljs ont apporté le rechargement à chaud au développement frontend, une idée issue directement de la tradition Lisp consistant à développer au contact d'un système en cours d'exécution.

Faut-il apprendre Lisp ?

La réponse honnête dépend de ce que vous voulez en retirer.

Si vous voulez devenir un meilleur programmeur en élargissant votre façon de penser le code : oui, absolument. Apprendre Lisp, et plus précisément penser en termes de transformation de données, de structures récursives et de code-comme-donnée, changera votre manière d'aborder les problèmes dans n'importe quel langage. C'est comme apprendre la programmation fonctionnelle : même si vous n'utilisez jamais Haskell en production, ces concepts vous feront écrire de meilleurs programmes en Python et en JavaScript.

Si vous voulez construire un logiciel de production avec Lisp : Clojure est le choix pragmatique. Il dispose d'un véritable écosystème, d'une communauté professionnelle et d'entreprises qui le font tourner à grande échelle (Nubank, Walmart, CircleCI). Common Lisp est viable mais de niche : vous passerez plus de temps sur l'infrastructure et moins sur votre problème réel. Scheme et Racket sont excellents pour l'enseignement et la recherche en langages, mais moins pratiques pour le développement généraliste.

Si vous êtes curieux sans être prêt à vous engager, lisez du code Clojure. Pas des tutoriels, du code de production réel. Observez comment les développeurs Clojure composent de petites fonctions, comment ils utilisent les macros de threading (-> et ->>) pour construire des pipelines de données, comment ils modélisent les domaines avec de simples maps plutôt qu'avec des classes. Vous y verrez un style de programmation plus concis, plus composable et davantage centré sur le flux de données que ce que l'on trouve dans les bases de code POO classiques.

La longévité de Lisp n'a rien de nostalgique. Elle résulte du fait d'avoir bien réussi quelques principes fondamentaux, que six décennies de conception de langages n'ont pas réussi à améliorer. Les parenthèses sont bizarres. L'écosystème est plus réduit qu'on ne le souhaiterait. Mais les idées sont intemporelles, et si vous prenez le temps de les explorer, vous vous surprendrez à mieux écrire dans le langage que vous utilisez au quotidien.