Découvrez comment l’approche symbolique de John McCarthy et les idées de conception de Lisp — listes, récursion et garbage collection — ont influencé l’IA et la programmation moderne.

Il ne s’agit pas d’une visite de musée sur « l’IA d’antan ». C’est une leçon d’histoire pratique pour quiconque construit des logiciels — programmeurs, tech leads et responsables produit — parce que les idées de John McCarthy ont façonné notre manière de penser à l’utilité des langages de programmation.
Lisp n’était pas seulement une nouvelle syntaxe. C’était le pari que le logiciel pouvait manipuler des idées (pas seulement des nombres) et que des choix de conception de langage pouvaient accélérer la recherche, l’itération produit et tout un écosystème d’outils.
Une manière utile de lire l’héritage de McCarthy est de se poser la question qui reste pertinente aujourd’hui : jusqu’à quel point peut-on transformer une intention directement en système exécutable — sans se noyer dans le boilerplate, la friction ou la complexité accidentelle ? Cette question résonne du REPL de Lisp jusqu’aux flux modernes « chat→app ».
John McCarthy est connu non seulement pour avoir aidé à lancer l’IA comme domaine de recherche, mais pour avoir insisté sur un type spécifique d’IA : des systèmes capables de manipuler des idées, pas seulement de calculer des réponses. Au milieu des années 1950, il a organisé le Dartmouth Summer Research Project (où le terme « intelligence artificielle » a été proposé) et a influencé les travaux à MIT puis à Stanford. Sa contribution la plus durable peut être la question qu’il n’a cessé de poser : et si le raisonnement lui-même pouvait s’exprimer comme un programme ?
Les premiers succès de l’informatique étaient surtout numériques : tables balistiques, simulations d’ingénierie, optimisation et statistiques. Ces problèmes se prêtent bien à l’arithmétique.
McCarthy visait autre chose. Le raisonnement humain travaille souvent avec des notions comme « si », « parce que », « appartient à », « est une sorte de », et « toutes les choses qui satisfont ces conditions ». Ce ne sont pas des choses qu’on représente naturellement par des flottants.
L’approche de McCarthy considérait le savoir comme des symboles (noms, relations, catégories) et le raisonnement comme des transformations basées sur des règles opérant sur ces symboles.
Une façon haute-niveau de l’imaginer : les approches numériques répondent « combien ? » tandis que les approches symboliques cherchent à répondre « qu’est-ce que c’est ? » et « qu’est-ce qui découle de ce que nous savons ? »
Si l’on croit que le raisonnement peut être rendu programmable, il faut un langage qui puisse représenter confortablement des expressions comme des règles, des énoncés logiques et des relations imbriquées — puis les traiter.
Lisp a été conçu pour cet objectif. Plutôt que d’imposer des structures de données rigides et préfabriquées, Lisp rendait naturel de représenter le code et le savoir sous une forme similaire. Ce choix n’était pas un élégant exercice académique : c’était un pont pratique entre décrire une pensée et exécuter une procédure, exactement le pont que McCarthy voulait voir l’IA franchir.
Quand McCarthy et les premiers chercheurs en IA parlaient de « symbolique », ils ne parlaient pas de mathématiques mystérieuses. Un symbole est simplement une étiquette signifiante : un nom comme customer, un mot comme hungry, ou une balise comme IF et THEN. Les symboles comptent parce qu’ils permettent à un programme de travailler avec des idées (catégories, relations, règles) plutôt qu’uniquement avec des nombres bruts.
Une image simple : les tableurs sont excellents quand votre monde est fait de colonnes et d’arithmétique. Les systèmes symboliques sont meilleurs quand votre monde est fait de règles, de catégories, d’exceptions et de structure.
Dans beaucoup de programmes, la différence entre 42 et "age" n’est pas le type de données — c’est ce que la valeur représente. Un symbole vous donne quelque chose que vous pouvez comparer, stocker et combiner sans perdre le sens.
Cela rend naturel de représenter des assertions comme « Paris est une ville » ou « si la batterie est faible, trouve un chargeur ».
Pour faire quelque chose d’utile avec des symboles, il faut de la structure. Lisp a popularisé une structure très simple : la liste. Une liste est juste un groupe ordonné d’éléments, et ces éléments peuvent eux-mêmes être des listes. Avec cette seule idée, on peut représenter des phrases, des formes et des connaissances en arbre.
Voici un minuscule exemple conceptuel (style Lisp) :
(sentence (subject robot) (verb needs) (object power))
Cela se lit presque comme de l’anglais : une phrase composée d’un sujet, d’un verbe et d’un objet. Parce que c’est structuré, un programme peut repérer (subject robot) ou remplacer (object power) par autre chose.
Une fois l’information dans des structures symboliques, les tâches classiques de l’IA deviennent abordables :
Le changement clé est que le programme ne se contente pas de calculer : il manipule des morceaux de savoir signifiants sous une forme qu’il peut inspecter et transformer.
Les décisions de conception de Lisp ne sont pas restées cantonnées à l’académie. Elles ont influencé la construction d’outils et la rapidité d’exploration des idées :
Ces traits produisent des écosystèmes où l’expérimentation coûte peu, où les prototypes deviennent des produits plus vite, et où les équipes peuvent s’adapter quand les besoins changent.
Lisp est né d’un problème pratique : comment écrire des programmes qui travaillent avec des symboles aussi naturellement qu’ils travaillent avec des nombres ?
McCarthy ne cherchait pas à construire une « meilleure calculatrice ». Il voulait un langage où une expression comme (is (parent Alice Bob)) puisse être stockée, inspectée, transformée et raisonnée aussi facilement que (+ 2 3).
La priorité était de rendre l’information symbolique facile à représenter et à manipuler. Cela a conduit à un accent sur les listes et les structures arborescentes, car elles correspondent bien à ce que les humains utilisent déjà pour exprimer du sens : phrases, règles logiques, catégories imbriquées et relations.
Un autre objectif était de garder le noyau du langage petit et cohérent. Quand un langage a moins de « cas spéciaux », on passe moins de temps à mémoriser des règles et plus de temps à composer des idées. Lisp a misé sur un petit nombre de briques élémentaires pouvant se combiner en abstractions plus larges.
Une idée clé était que programmes et données peuvent partager la même structure. En clair : si vos données sont des listes imbriquées, votre programme peut aussi être une liste imbriquée.
Cela signifie que vous pouvez :
Lisp a aussi popularisé un état d’esprit : les langages n’ont pas à être universels et identiques pour tous les usages. Ils peuvent être conçus autour d’un domaine problème — comme le raisonnement, la recherche et la représentation du savoir — et avoir pourtant un impact durable sur la programmation générale.
Les S-expressions (pour symbolic expressions) sont l’idée signature de Lisp : une façon unique et cohérente de représenter code et données comme des listes imbriquées.
En un coup d’œil, une S-expression est juste des parenthèses autour d’éléments — certains sont des atomes (noms et nombres), d’autres sont des listes. Cette règle « listes dans des listes » est tout l’enjeu.
Parce que la structure est uniforme, les programmes Lisp sont construits à partir des mêmes briques jusqu’en bas. Un appel de fonction, un morceau de configuration et une structure de programme peuvent tous s’exprimer comme une liste.
Ce principe rapporte vite :
Même si vous n’écrivez jamais de Lisp, c’est une leçon importante : quand un système est bâti sur une ou deux formes prévisibles, on passe moins de temps à lutter contre les cas limites et plus de temps à construire.
Les S-expressions favorisent la composition car des petites pièces lisibles se combinent naturellement en plus grandes. Quand votre programme est « juste des listes imbriquées », combiner des idées revient souvent à imbriquer une expression dans une autre ou à assembler des listes à partir de pièces réutilisables.
Cela incite à un style modulaire : écrire de petites opérations qui font une chose, puis les empiler pour exprimer une intention plus large.
L’inconvénient évident est l’étrangeté. Pour beaucoup de débutants, une syntaxe chargée de parenthèses paraît étrange.
Mais l’avantage est la prévisibilité : une fois qu’on comprend les règles d’imbrication, on voit de façon fiable la structure d’un programme — et les outils aussi. Cette clarté explique en grande partie pourquoi les S-expressions ont eu des conséquences bien au-delà de Lisp lui-même.
La récursion se comprend aisément par une métaphore du quotidien : nettoyer une pièce en en faisant de plus petites « sous-pièces ». On ne tente pas de tout résoudre d’un coup. On prend un objet, on le remet à sa place, puis on répète la même action sur ce qui reste. Les étapes sont simples ; la puissance vient de leur répétition jusqu’à ce qu’il n’y ait plus rien à faire.
Lisp exploite cette idée parce qu’une grande partie de ses données est naturellement faite de listes : une liste a une « première chose » et « le reste ». Cette forme convient parfaitement à la pensée récursive.
Pour traiter une liste, on gère le premier élément, puis on applique la même logique au reste. Quand la liste est vide, on s’arrête — c’est le moment propre « plus rien à faire » qui rend la récursion bien définie plutôt que mystérieuse.
Imaginez que vous voulez la somme d’une liste de nombres.
C’est tout. La définition se lit comme de l’anglais, et la structure du programme reflète l’idée.
L’IA symbolique représente souvent des expressions comme des structures arborescentes (un opérateur avec des sous-expressions). La récursion est un moyen naturel de « parcourir » cet arbre : évaluer la partie gauche de la même façon que la partie droite, et continuer jusqu’à atteindre une valeur simple.
Ces patterns ont influencé la programmation fonctionnelle ultérieure : petites fonctions, cas de base clairs et transformations de données faciles à raisonner. Même hors Lisp, l’habitude de décomposer le travail en « faire une étape, puis répéter sur le reste » conduit à des programmes plus propres et moins d’effets de bord cachés.
Les premiers programmeurs devaient souvent gérer la mémoire à la main : allouer de l’espace, suivre qui en « est propriétaire » et se souvenir de le libérer au bon moment. Ce travail ne ralentit pas seulement le développement — il crée une catégorie de bugs difficilement reproductibles et faciles à expédier : fuites qui dégradent silencieusement les performances, et pointeurs pendants qui font crasher un programme bien après l’erreur initiale.
John McCarthy a introduit le ramassage des ordures pour Lisp comme moyen de permettre aux programmeurs de se concentrer sur le sens plutôt que sur la paperasserie.
De façon simple, la GC trouve automatiquement les zones mémoire qui ne sont plus accessibles depuis le programme en cours — des valeurs que rien ne peut plus utiliser — et récupère cet espace.
Au lieu de se demander « avons-nous libéré chaque objet exactement une fois ? », la GC repose la question sur « cet objet est-il encore accessible ? ». Si le programme ne peut pas l’atteindre, c’est de la poubelle.
Pour le travail en IA symbolique, les programmes Lisp créent fréquemment beaucoup de listes, d’arbres et de résultats intermédiaires éphémères. La gestion manuelle de la mémoire transformerait l’expérimentation en une lutte constante avec le nettoyage des ressources.
La GC change l’expérience quotidienne :
L’idée clé est qu’une caractéristique du langage peut être un multiplicateur d’équipe : moins d’heures passées à déboguer des corruptions mystérieuses signifie plus de temps pour améliorer la logique réelle.
Le choix de McCarthy n’est pas resté confiné à Lisp. Beaucoup de systèmes ultérieurs ont adopté la GC (et ses variantes) parce que le compromis paie souvent : Java, C#, Python, les runtimes JavaScript et Go s’appuient tous sur la collecte automatique pour rendre le développement à grande échelle plus sûr et plus rapide — même quand la performance est une priorité.
Dans Lisp, une expression est un morceau de code écrit sous une forme cohérente (souvent une liste). L’évaluation est simplement le processus qui décide ce que cette expression veut dire et ce qu’elle produit.
Par exemple, quand vous écrivez quelque chose comme « additionne ces nombres » ou « appelle cette fonction avec ces entrées », l’évaluateur suit un petit ensemble de règles pour tourner cette expression en résultat. Pensez-y comme à l’arbitre du langage : il décide quoi faire ensuite, dans quel ordre et quand s’arrêter.
Le coup clé de McCarthy n’était pas seulement d’inventer une nouvelle syntaxe — c’était de garder le « moteur de sens » compact et régulier. Quand l’évaluateur est construit sur quelques règles claires, deux choses utiles suivent :
Cette cohérence explique en partie pourquoi Lisp est devenu un terrain d’expérimentation pour l’IA symbolique : les chercheurs pouvaient essayer rapidement de nouvelles représentations et structures de contrôle, sans attendre qu’une équipe de compilation refonde le langage.
Les macros sont le moyen de Lisp d’automatiser des formes de code répétitives, pas seulement des valeurs répétitives. Là où une fonction vous évite de répéter des calculs, une macro vous évite de répéter des structures — des motifs courants comme « faire X, mais aussi logger », ou « définir un mini-langage pour des règles ».
L’effet pratique est que Lisp peut faire pousser de nouvelles commodités de l’intérieur. Beaucoup d’outils modernes reprennent cette idée — systèmes de templates, générateurs de code et métaprogrammation — parce qu’ils visent le même objectif : expérimentation plus rapide et expression d’intention plus claire.
Si vous voulez voir comment cet état d’esprit a influencé des workflows de développement quotidiens, voyez /blog/the-repl-and-fast-feedback-loops.
Une grande partie de l’attrait de Lisp ne venait pas seulement du langage — mais de la façon dont on travaillait avec lui. Lisp a popularisé le REPL : Read–Eval–Print Loop. En termes quotidiens, c’est comme avoir une conversation avec l’ordinateur. Vous tapez une expression, le système l’exécute immédiatement, affiche le résultat, et attend votre prochaine entrée.
Au lieu d’écrire tout un programme, le compiler, l’exécuter, puis chercher ce qui a cassé, vous pouvez essayer des idées pas à pas. Vous définissez une fonction, la testez avec quelques entrées, l’ajustez, et testez de nouveau — tout cela en quelques secondes.
Ce rythme encourage l’expérimentation, ce qui comptait beaucoup pour le travail en IA où on ne connaît souvent pas la bonne approche d’emblée.
La rétroaction rapide transforme les « gros paris » en « petites vérifications ». Pour la recherche, elle facilite l’exploration d’hypothèses et l’inspection de résultats intermédiaires.
Pour le prototypage produit, elle réduit le coût de l’itération : vous pouvez valider le comportement avec des données réelles rapidement, repérer les cas limites plus tôt et affiner les fonctionnalités sans attendre de longs cycles de build.
C’est aussi pourquoi les outils de type vibe-coding sont séduisants : ils comprimment agressivement la boucle de rétroaction. Par exemple, Koder.ai utilise une interface par chat (avec une architecture agentée en coulisses) pour transformer l’intention produit en code web, backend ou mobile fonctionnel — faisant souvent de la boucle « essayer → ajuster → réessayer » quelque chose de proche d’un REPL plutôt que d’un pipeline traditionnel.
L’idée du REPL réapparaît aujourd’hui dans :
Des outils différents, même principe : raccourcir la distance entre la pensée et la visualisation.
Les équipes profitent le plus des workflows proches du REPL quand elles explorent des exigences incertaines, construisent des fonctionnalités data-heavy, conçoivent des API ou déboguent une logique délicate. Si le travail implique un apprentissage rapide — des utilisateurs, des données ou des cas limites — la rétroaction interactive n’est pas un luxe, c’est un multiplicateur.
Lisp n’a pas « gagné » en devenant la syntaxe quotidienne de tout le monde. Il a gagné en semant des idées qui sont devenues la norme dans de nombreux écosystèmes.
Des concepts que Lisp considérait comme par défaut — fonctions comme valeurs, usage intensif d’opérations d’ordre supérieur, préférence pour la composition de petites parties — apparaissent désormais largement. Même des langages qui ne ressemblent pas à Lisp encouragent les transformations map/filter, les habitudes de données immutables et la pensée récursive (souvent via des itérateurs ou des folds).
Le changement mental clé est : traitez les transformations de données comme des pipelines, et le comportement comme quelque chose que vous pouvez passer.
Lisp a rendu naturel de représenter les programmes comme des données. Cet état d’esprit se retrouve aujourd’hui dans la façon dont on manipule des AST (arbres de syntaxe abstraite) pour les compilateurs, formatters, linters et générateurs de code. Quand vous travaillez avec un AST, vous faites le proche cousin du « code comme données », même si les structures sont des objets JSON, des nœuds typés ou des graphes de bytecode.
La même approche symbolique alimente l’automatisation pratique : formats de configuration, systèmes de templates et pipelines de build s’appuient tous sur des représentations structurées que les outils peuvent inspecter, transformer et valider.
Les langages de la famille Lisp (au sens large : Lisps contemporains et outils inspirés de Lisp) continuent d’influencer la conception des DSL internes — petits langages ciblés pour tests, déploiement, traitement de données ou UI.
Hors Lisp, les systèmes de macros, bibliothèques de métaprogrammation et frameworks de génération cherchent le même résultat : étendre le langage pour coller au problème.
Une conclusion pragmatique : les préférences de syntaxe changent, mais les idées durables — structure symbolique, fonctions composables et extensibilité — restent payantes à long terme.
La réputation de Lisp oscille entre « brillant » et « illisible », souvent sur la base d’impressions de seconde main plutôt que d’expériences quotidiennes. La réalité est plus ordinaire : Lisp fait des choix puissants dans le bon contexte et gênants dans d’autres.
Pour les nouveaux venus, la syntaxe uniforme de Lisp donne l’impression d’examiner l’« intérieur » d’un programme plutôt que sa surface polie. Cette gêne est réelle, surtout si vous êtes habitué à des langages où la syntaxe sépare visuellement différents constructs.
Historiquement, cependant, la structure de Lisp est aussi son avantage : code et données partagent la même forme, ce qui rend les programmes plus faciles à transformer, générer et analyser. Avec un bon support d’éditeur (indentation, navigation structurelle), le code Lisp se lit souvent par sa forme plutôt que par le comptage des parenthèses.
Un stéréotype courant veut que Lisp soit lent. Historiquement, certaines implémentations souffraient face aux langages bas niveau, et les fonctionnalités dynamiques peuvent ajouter une overhead.
Mais il n’est pas exact de traiter « Lisp » comme un profil de performance unique. Beaucoup de systèmes Lisp ont depuis longtemps des compilateurs, des déclarations de types et des optimisations sérieuses. Le meilleur cadrage est : de combien de contrôle avez-vous besoin sur la disposition mémoire, la latence prévisible ou le débit brut — et votre implémentation Lisp cible-t-elle ces besoins ?
Une critique juste est l’adéquation de l’écosystème. Selon le dialecte et le domaine, les bibliothèques, outils et viviers de talents peuvent être plus restreints que les stacks grand public. Cela peut peser davantage que l’élégance du langage si vous devez livrer rapidement avec une large équipe.
Plutôt que de juger Lisp sur ses stéréotypes, évaluez ses idées sous-jacentes : structure uniforme, développement interactif et macros comme outil pour construire des abstractions spécifiques au domaine. Même si vous ne déployez jamais un système Lisp, ces concepts affinent votre réflexion sur la conception des langages et la manière d’écrire du code dans n’importe quel langage.
McCarthy ne nous a pas seulement laissé un langage historique — il a laissé un ensemble d’habitudes qui rendent encore le logiciel plus facile à changer, expliquer et étendre.
Avant de choisir une bibliothèque ou une architecture, prenez une fonctionnalité réelle — disons « règles de réduction » ou « routage de tickets support » — et dessinez-la comme un arbre. Puis réécrivez cet arbre comme des listes imbriquées (ou du JSON). Demandez-vous : quels sont les nœuds, quelles sont les feuilles, et quelles transformations sont nécessaires ?
Même sans Lisp, vous pouvez adopter l’état d’esprit : construisez des représentations de type AST, utilisez la génération de code pour lier des éléments répétitifs, et standardisez des pipelines data-first (parse → transform → evaluate). Beaucoup d’équipes en tirent profit simplement en rendant explicites leurs représentations intermédiaires.
Si le principe REPL vous séduit mais que vous livrez des features dans des stacks mainstream, vous pouvez emprunter l’esprit via des outils : boucles d’itération courtes, snapshots/rollback et planification explicite avant exécution. Koder.ai, par exemple, inclut un mode de planification plus des snapshots et rollback pour rendre l’itération rapide plus sûre — un écho opérationnel du principe Lisp « changer vite, mais garder le contrôle ».
L’influence durable de McCarthy est la suivante : la programmation devient plus puissante quand nous rendons le raisonnement lui-même programmable — et quand nous gardons le chemin de l’idée au système exécutable aussi direct que possible.
La pensée symbolique représente directement des concepts et des relations (par exemple « client », « est-un », « dépend-de », « si…alors… »), puis applique des règles et des transformations sur ces représentations.
C’est particulièrement utile quand votre problème regorge de structure, d’exceptions et de sens (moteurs de règles, planification, compilateurs, configuration, logique de flux), et non seulement d’arithmétique.
McCarthy a défendu l’idée que le raisonnement peut s’exprimer comme des programmes, et pas seulement comme des calculs.
Cette perspective a influencé :
Les listes sont une manière minimale et flexible de représenter des « choses faites de parties ». Comme les éléments d’une liste peuvent être eux-mêmes des listes, on obtient naturellement des structures arborescentes.
Cela facilite :
Les S-expressions fournissent une forme unique et uniforme pour le code et les données : des listes imbriquées.
Cette uniformité simplifie les systèmes car :
Une macro automatise des structures de code répétitives, pas seulement des calculs répétitifs.
On utilise une macro quand on veut :
Si vous avez seulement besoin d’une logique réutilisable, une fonction est généralement préférable.
Le ramassage des ordures (garbage collection) récupère automatiquement la mémoire devenue inaccessible, ce qui réduit des catégories entières de bugs (pointeurs pendants, doubles libérations).
C’est particulièrement utile quand un programme crée beaucoup de structures éphémères (listes/arbres/AST), car on peut prototyper et refactorer sans concevoir d’emblée une stratégie manuelle de propriété mémoire.
Un REPL raccourcit la boucle « penser → essayer → observer ». Vous pouvez définir une fonction, l’exécuter, l’ajuster et réexécuter immédiatement.
Pour obtenir le même avantage dans des stacks non-Lisp :
Lecture connexe : /blog/the-repl-and-fast-feedback-loops
Beaucoup des flux de travail modernes reprennent les mêmes idées :
Même sans déployer Lisp, vous utilisez probablement chaque jour des habitudes héritées de Lisp.
Les compromis réels incluent :
Approche pratique : évaluez l’adéquation au domaine et aux contraintes plutôt que la réputation.
Essayez cet exercice (10 minutes) :
Cela montre souvent où des patterns « code-comme-donnée », moteurs de règles ou configs de type DSL peuvent simplifier le système.