🛸 Cours 7 — Au-delà du C :
langages, paradigmes & le monde qu'ils construisent

Du rasoir au microscope : on n'utilise pas le C pour faire de la grammaire — on utilise le bon outil pour le bon produit.

1. L'outil n'est pas le produit

Depuis le début de ce cours, on a construit des trucs avec du C : une factorielle, une machine virtuelle, un parseur de s-expressions, un évaluateur avec environnements.

Et là, il se passe un truc intéressant : on a aussi écrit un interpréteur LISP. Pas pour apprendre LISP (quoique), mais parce que LISP est un méta-outil : il sert à fabriquer d'autres outils. Le C, lui, sert à fabriquer des trucs qui tournent vite. Les deux ensembles, ça devient un atelier complet.

🔧 C (proche hardware)
⚡ Rapidité, contrôle mémoire, syscalls
+
λ LISP (homoiconique)
🧠 Métaprogrammation, DSL, calcul formel

Ce qu'on a dans les mains, ce n'est pas « un langage ». C'est une caisse à outils.

🧰 « Si le seul outil que vous avez est un marteau, tout ressemble à un clou. » — Maslow (1966)
« Si vous avez un marteau et un tournevis, soudain vous voyez des vis partout. Et vous pouvez réparer une chaise. » — Nous (2026)

Ce cours 7 n'est pas un cours de plus. C'est une méta-réflexion sur ce qu'on a construit, pourquoi on l'a construit, et comment ces idées se retrouvent — parfois mot pour mot — dans des logiciels qui changent le monde.

2. Homoiconité : quand le code devient donnée (et vice-versa)

Une idée qui retourne le cerveau

Homoiconicitė (du grec homo = même, icon = représentation) : dans un langage homoiconique, le code source et les structures de données ont la même représentation.

Concrètement : en LISP, une expression comme (+ 1 2) est à la fois :

Tu peux écrire du code qui fabrique du code comme tu fabriques une liste. Un macro, c'est juste une fonction qui renvoie du code, que l'évaluateur exécute ensuite. C'est d'une simplicité diabolique.

;; Donnée = une liste
'(+ 1 2)       ; quote:  c'est une liste, pas du code
;; → (+ 1 2)

;; Code = pareil, mais évalué
(+ 1 2)        ; évalué → 3

;; On peut fabriquer du code :
(list '+ 1 2)  ; → (+ 1 2)
(eval (list '+ 1 2))  ; → 3
/* En C, le code c'est du texte,
   les données c'est des struct.
   Jamais les deux ne se mélangent.
   (sauf avec des pointeurs de fonction,
    mais c'est pas de l'homoiconicité) */

int x = 1 + 2;  // du code
int tab[] = {43, 42, 41}; // des données

// Essayer de traiter le code comme données ?
// Vous pouvez écrire un interpréteur,
// mais le langage ne le fait pas tout seul.
💡 L'idée géniale : si code et données ont la même forme, alors un programme peut réfléchir sur lui-même. C'est la réflexivité. Ça permet les macros, les DSL (Domain-Specific Languages), les transformations de code automatiques, et les compilateurs écrits… dans le langage lui-même.

Et alors ? Pourquoi c'est important ?

Parce que l'homoiconicité est le super-pouvoir qui se cache derrière tous les grands systèmes de calcul formel et numérique. Regardez :

🧙 Le C, c'est un tournevis : solide, efficace, fait le job. LISP, c'est un couteau suisse dont le manche est aussi un tournevis, aussi un ouvre-boîte, aussi un cure-dent, et si vous le regardez bizarrement, il devient une scie. L'homoiconicité, c'est le mécanisme qui permet ça.

3. Les descendants du trône : de LISP à Mathematica, R, Julia, Clojure…

LISP, né en 1958 (même âge que le C, en fait — et pourtant ils n'ont pas du tout la même tête), est le deuxième plus vieux langage de haut niveau encore utilisé (après Fortran). Mais son héritage est partout.

Mathematica / Wolfram Language (1988)

Mathematica est le leader mondial du calcul formel. Son langage interne (le Wolfram Language) est directement inspiré de LISP. Toute expression est une liste : Plus[1, 2] au lieu de (+ 1 2), mais c'est la même structure d'arbre.

;; LISP
(+ 1 (* 2 3))    ; → 7

(* Wolfram Language *)
Plus[1, Times[2, 3]]  ; → 7

(* Et Mathematica peut manipuler ça comme des données *)
FullForm[1 + 2x]  ; → Plus[1, Times[2, x]]

Les capacités de calcul formel de Mathematica (dérivation, intégration, simplification, résolution d'équations) reposent sur le fait que les expressions sont des arbres manipulables par le programme lui-même. C'est exactement le principe de notre évaluateur LISP.

🧮 Mathematica, c'est le LISP de quelqu'un qui a dit : « les parenthèses, c'est moche, je vais mettre des crochets. » Et il a vendu ça plusieurs milliers de dollars la licence. Le LISP, lui, il est gratuit, mais tout le monde a peur des parenthèses. La leçon : le design visuel compte.

R (1993) — la statistique made in LISP

R est le langage de référence pour les statistiques et la visualisation de données. Son cœur est un dialecte de Scheme (un descendant de LISP). Chaque expression R est une liste :

;; LISP
(+ 1 2)
(mean (c 1 2 3 4 5))

# R
`+`(1, 2)          # Oui, c'est un appel de fonction comme une liste
mean(c(1, 2, 3, 4, 5))  # Pareil

Le ~ de R (formules de modèle linéaire : y ~ x + z) est une macrologie LISP déguisée : la formule est une expression non évaluée, que R réécrit et manipule avant de l'interpréter. Les data.frame, les list, tout est conçu sur le modèle code = donnée.

📊 R, c'est le langage préféré des statisticiens qui ne veulent pas avouer qu'ils font du LISP. « Non non, je fais des boxplots, pas de la métaprogrammation. » — et pourtant la moitié des bugs en R viennent de la non-évaluation paresseuse (quand une formule capture une variable qui n'est pas encore définie).

Julia (2012) — la vélocité du C, l'élégance du LISP

Julia est un langage de calcul haute performance qui veut le beurre (la vitesse du C) et l'argent du beurre (la flexibilité des langages dynamiques).

Le secret de Julia ? Il est compilé à la volée (JIT) via LLVM, et son système de macros est directement pompé de LISP. Les macros Julia manipulent l'AST (Abstract Syntax Tree) — exactement comme notre évaluateur manipule les s-expressions :

# Julia : macro style LISP
macro assert(expr)
    # expr est une expression, pas encore évaluée
    # On peut l'inspecter, la modifier…
    quote
        if !($expr)
            println("Assertion failed: ", $(string(expr)))
        end
    end
end

;; Équivalent LISP (notre let.c !)
(define-macro assert (expr)
  `(if (not ,expr)
       (display (string-append "Assertion failed: "
                                ,(symbol->string expr)))))

Julia peut tourner du code aussi vite que du C optimisé parce que ses types sont optionnellement annotés et que le compilateur JIT les utilise pour produire du code machine spécialisé. Mais en dessous, c'est LISP qui respire.

💡 Leçon : C et LISP ne sont pas en compétition. Ils opèrent à des niveaux différents. Un bon système, c'est les deux : C pour le moteur, LISP pour le pilotage. Julia l'a compris : le moteur est en C/C++/LLVM, le pilotage est en LISP-like.

Clojure (2007) — LISP sur la JVM

Clojure est un dialecte LISP moderne qui tourne sur la machine virtuelle Java (JVM). Il apporte l'homoiconicité du LISP à l'écosystème Java — et ça marche tellement bien que des boîtes comme Walmart, Netflix ou Apple l'utilisent en production.

Tout est immutable par défaut (plus de bugs de mutation), les structures de données sont persistantes (partage de mémoire), et les macros sont toujours là, bien sûr.

;; Clojure : un web server en 5 lignes
(require '[ring.adapter.jetty :refer [run-jetty]])
(require '[ring.util.response :refer [response]])

(defn handler [req]
  (response "Hello from Clojure! (LISP on the JVM)"))

(run-jetty handler {:port 3000})
;; C'est quoi cette syntaxe ? Des parenthèses ?
;; Oui. Et ça sert la page en moins de temps que votre café.
☕ Clojure, c'est la réponse à la question que personne n'avait posée : « Et si on mettait du LISP dans Java ? » Et pourtant, c'est génial. C'est un peu comme mettre une capote de décapotable sur une voiture de course : on peut pas voir les roues, mais ça roule drôlement bien.

AutoCAD / AutoLISP (1986) — le LISP qui dessine des ponts

AutoCAD est la référence mondiale de la CAO (Conception Assistée par Ordinateur) en mécanique, architecture, génie civil. Et son langage de script s'appelle AutoLISP. Oui : LISP.

Depuis 1986, AutoCAD embarque un interpréteur LISP pour permettre aux ingénieurs d'automatiser leurs tâches de dessin. Tu veux générer 500 poutres avec des espacements paramétriques ? Tu écris une boucle en AutoLISP. Tu veux un script qui dessine un escalier en colimaçon ? C'est 20 lignes de LISP.

;; AutoLISP : dessiner un cercle
(command "circle" (list 0 0 0) 5.0)

;; AutoLISP : générer une rangée de piliers
(setq i 0)
(repeat 10
  (command "box" (list (* i 10) 0 0) (list (+ (* i 10) 2) 10 2))
  (setq i (1+ i))
)

Pourquoi LISP pour un logiciel de dessin ? Parce que les dessins techniques sont des structures arborescentes (blocs, calques, entités), et que LISP manipule les arbres comme personne. L'homoiconicité permet de traiter un plan comme du code, et du code comme un plan.

🏗️ AutoLISP, c'est la preuve qu'un langage des années 50 peut concevoir des gratte-ciel en 2026. La tour Burj Khalifa ? Dessinée avec des outils qui contiennent du LISP. Le viaduc de Millau ? Pareil. La prochaine fois que vous traversez un pont, remerciez John McCarthy (inventeur du LISP) et les parenthèses.

4. C + LISP : le duo qui gagne

Maintenant, regardez ce qu'on a construit dans ce cours :

ComposantLangageFait quoiRetrouvé dans
VM à pileCExécute du bytecode rapidementJVM, CLR, LuaJIT, Python VM
Parseur s-expressionsCTransforme du texte en arbreWolfram Kernel, R parser, Julia parser
Évaluateur (envs chaînés)CÉvalue des expressions LISPAutoLISP, Emacs LISP, tout REPL
Compilateur calc → bytecodeCCompile du LISP en bytecode VMJulia → LLVM, Mathematica → bytecode

Chaque brique qu'on a écrite est une version miniature mais fonctionnelle de ce qui fait tourner le monde numérique. La VM ? C'est la JVM en plus petit. Le parseur s-expression ? C'est le cœur de Mathematica. L'évaluateur avec environnements ? C'est comment tourne R, Julia, Python.

📦 Code C
⚡ Performances natives
+
λ Structure LISP
🧠 Expressivité maximale
=
🚀 Application réelle
🤝 Le C te donne la vitesse. Le LISP te donne la puissance. Ensemble, ils forment les Power Rangers de l'informatique. Sauf que le C est le Ranger rouge (celui qui fonce dans le tas) et le LISP est le Ranger bleu (celui qui a un plan). Et ils ont le même costume, en fait, mais celui du LISP a plus de parenthèses.

5. Pourquoi ce cours existe

Ce septième cours est un point d'étape. On a passé six cours à construire des briques. Maintenant, on lève la tête et on regarde ce qu'on a fait.

Ce que vous avez appris (sans le savoir)

Et tout ça, vous l'avez fait en C, un langage qui a 50 ans, qui tient dans une poche, et qui fait tourner 99% du monde numérique. Pas parce que c'est le langage le plus élégant (il ne l'est pas), mais parce que c'est le plus fondamental.

🔍 Le programmeur qui ne connaît qu'un seul langage est comme un charpentier qui ne sait utiliser qu'un marteau : il peut construire une cabane, mais pas une horloge. Le programmeur qui connaît le C et le LISP peut construire n'importe quoi — parce qu'il a vu les deux extrêmes et peut choisir le bon outil à chaque niveau.
🎓 Vous n'avez pas appris « le C ». Vous avez appris le socle. Vous n'avez pas appris « le LISP ». Vous avez appris un paradigme. Vous avez monté des escaliers qui traversent les âges de l'informatique. Et maintenant, vous pouvez regarder un langage comme Julia ou R et dire : « Tiens, le même pattern que notre let.c. » C'est ça, la vraie compétence : voir le pattern.

6. Tableau de correspondance

Pour que tout soit clair, voici comment chaque concept du cours se retrouve dans des systèmes réels :

Notion du coursNotre implémentationDans le monde réel
VM à pile vm.c JVM, CPython, LuaJIT, WebAssembly
Parseur s-exp calc.c (parse) Wolfram Kernel, R parse(), Julia Meta.parse()
Évaluateur let.c AutoLISP, Emacs Lisp, R eval()
Macros / code = donnée let.c (éval liste) Clojure macros, Julia @macro, R non-standard eval
Compilateur LISP → bytecode calc.c (-vm) Julia → LLVM, Mathematica → WVM, Clojure → JVM
Transpileur transpile.c TypeScript → JS, Python → C, tout transpileur
Gestion mémoire sexpression.c malloc/free, arena allocators, GC de Lua/Julia
🗺️ Si l'informatique était une carte du monde, le C serait la croûte terrestre (tout repose dessus), et le LISP serait la tectonique des plaques (tout bouge, se transforme, et crée des montagnes). Vous êtes maintenant en train de faire de la géologie. Beau programme pour un cours d'ingénieur, non ?

7. Du code à l'outil : deux bibliothèques LISP qui font du vrai travail

On a parlé de concepts, de paradigmes, de langages qui ont changé le monde. Mais si on s'arrêtait là, ce serait un peu comme un cours de menuiserie où on n'a montré que des photos de belles étagères sans jamais en construire une.

Alors construisons.

🪚 Assez parlé de la théorie du marteau — prenons-le, et cognons. (Attention aux doigts.)

On a deux fichiers LISP dans notre projet : mathematica.lisp (dérivation symbolique) et matlab.lisp (calcul vectoriel et matriciel). Ce sont de vraies bibliothèques, utilisables depuis let.c. Et la première chose qu'on remarque quand on les ouvre, c'est ça :

;;; mathematica.lisp — Dérivation symbolique et simplification
;;;
;;; (deriv expr var)        — dérive une expression arithmétique
;;; (simplify expr)         — simplifie une expression
;;; (deriv-simp expr var)   — dérive + simplifie

;;; matlab.lisp — Calcul numérique vectoriel et matriciel
;;;
;;; Vecteurs : addvec subvec mulvec scale dot sum norm norm1 cross
;;; Matrices : addmat submat transpose mulmat identity zeros ones
;;; Utilitaires : abs len append map range

Un en-tête de documentation. C'est la première chose qu'on écrit — et la dernière qu'on lit, mais c'est elle qui sauve une vie à 3h du matin quand on cherche pourquoi mulmat retourne une liste impropre. (Spoiler : c'est parce que () vaut 0 dans let.c. On a perdu 20 minutes là-dessus, et si ce n'était pas écrit, on y serait encore.)

📖 Règle numéro un de l'ingénierie : documenter son code, ce n'est pas pour les autres — c'est pour soi-même dans six mois. Quand vous reviendrez sur ce projet en janvier prochain, vous serez heureux d'avoir écrit ces commentaires. (Ou malheureux de ne pas l'avoir fait. Il n'y a pas de troisième option. C'est comme le café : tout le monde dit qu'il en a besoin, mais personne n'en prépare pour demain.)

Les fonctions proposées

Ouvrons nos deux bibliothèques et regardons ce qu'elles contiennent :

mathematica.lisp — le laboratoire de calcul formel

FonctionParamètresRetourneExemple
deriv expr var Expression dérivée (brute) (deriv '(Add (Var x) (Val 1)) 'x)(Add (Val 1) (Val 0))
simplify expr Expression simplifiée (simplify '(Add (Val 0) (Var x)))(Var x)
deriv-simp expr var Dérivée simplifiée (deriv-simp '(Mult (Var x) (Var x)) 'x)(Add (Var x) (Var x))

Les expressions sont représentées sous forme d'arbres : (Add (Mult (Val 3) (Var x)) (Val 1)) pour 3x + 1. Le calcul formel, c'est juste de la manipulation d'arbres — et LISP est littéralement fait pour ça puisque le code est un arbre.

🌳 En LISP, manipuler une expression mathématique, c'est comme faire de l'origami : le papier, c'est la syntaxe, et le pliage, c'est l'évaluation. Mathematica, c'est juste un origamiste très patient qui facture 5000 € la feuille.

matlab.lisp — le couteau suisse du calcul numérique

FonctionParamètresRetourneExemple
addveca ba + b (vec)(addvec '(1 2) '(3 4))
subveca ba − b (vec)(subvec '(5 7) '(4 5))
mulveca ba × b (vec, elem)(mulvec '(2 3) '(5 6))
scales vs × v (scalaire × vec)(scale 5 '(1 2 3))
dota ba · b (scalaire)(dot '(1 2) '(3 4))
sumvΣ v(sum '(1 2 3))
normv‖v‖₂(norm '(3 4))
crossa ba × b (3D)(cross '(1 0 0) '(0 1 0))
addmata ba + b (mat)(addmat '((1 2)(3 4)) '((5 6)(7 8)))
transposemmᵀ(transpose '((1 2)(3 4)))
mulmata ba × b (matriciel)(mulmat '((1 2)(3 4)) '((1 0)(0 1)))
identitynIₙ(identity 3)
zerosn m0n×m(zeros 2 3)
onesn m1n×m(ones 2 2)

Chaque fonction est définie en quelques lignes de LISP pur, sans une ligne de C supplémentaire. Quand on a un évaluateur, on peut étendre le langage dans le langage lui-même. C'est ça, la puissance d'un REPL : on construite des abstractions par-dessus des abstractions.

🧩 Le pattern « bootstrap » : le C fournit le socle (le parseur, l'évaluateur, les primitives). Le LISP construit le reste (les bibliothèques). Tout est fait dans le langage lui-même. C'est exactement comme ça que fonctionnent R, Julia, et Mathematica : un noyau en C/C++, des bibliothèques dans le langage lui-même.

Exemples d'utilisation

Voyons ces bibliothèques en action — avec de vraies sorties du programme :

🧮 Dérivation symbolique avec mathematica.lisp

$ ./let '(load mathematica.lisp)' '(deriv-simp (quote (Add (Mult (Val 3) (Var x)) (Val 1))) (quote x))'
; (load mathematica.lisp)
  -> #<environnement>
; (deriv-simp (quote (Add (Mult (Val 3) (Var x)) (Val 1))) (quote x))
  -> (Val 3)
; La dérivée de 3x + 1 est… 3. (Oui, c'est trivial. Mais c'est calculé par du LISP.)

Mathématiquement : d/dx (3x + 1) = 3. Notre bibliothèque le trouve toute seule. Pas de bête de somme. Pas d'appel à Wolfram Alpha. Juste notre évaluateur et 50 lignes de LISP.

$ ./let '(load mathematica.lisp)' '(deriv-simp (quote (Mult (Var x) (Var x))) (quote x))'
; (deriv-simp (quote (Mult (Var x) (Var x))) (quote x))
  -> (Add (Var x) (Var x))
; d/dx de x² = x + x (non simplifié — comme dirait un prof :
;   « c'est pas faux, mais on peut mieux faire »)
📝 La simplification laisse parfois à désirer (x + x au lieu de 2x). C'est normal : on a écrit le noyau en un après-midi, pas en dix ans comme Mathematica. Mais le principe est le même — et notre version coûte 0 € et zéro licences. Le prix de la connaissance, c'est le temps. Le prix du logiciel propriétaire, c'est de l'argent. Choisissez votre combat.

📊 Calcul matriciel avec matlab.lisp

$ ./let '(load matlab.lisp)' '(addvec (quote (1 2 3)) (quote (4 5 6)))'
; (addvec (quote (1 2 3)) (quote (4 5 6)))
  -> (5 7 9)

$ ./let '(load matlab.lisp)' '(mulmat (quote ((1 2) (3 4))) (quote ((5 6) (7 8))))'
; (mulmat (quote ((1 2) (3 4))) (quote ((5 6) (7 8))))
  -> ((19 22) (43 50))
; [[1 2] [3 4]] × [[5 6] [7 8]] = [[19 22] [43 50]]

$ ./let '(load matlab.lisp)' '(identity 3)'
; (identity 3)
  -> ((1 0 0) (0 1 0) (0 0 1))

$ ./let '(load matlab.lisp)' '(norm (quote (3 4)))'
; (norm (quote (3 4)))
  -> 5.0
; ‖(3,4)‖₂ = √(3² + 4²) = 5. Le théorème de Pythagore, version LISP.

On passe des listes à notre bibliothèque, elle les interprète comme des vecteurs ou des matrices, et elle effectue les calculs. Ce n'est pas encore NumPy — mais c'est le même pattern : une couche LISP par-dessus un noyau C.

🐍 NumPy, c'est 15 000 lignes de C avec une interface Python. Notre matlab.lisp, c'est 150 lignes de LISP avec une interface LISP. La différence ? L'échelle. Le principe ? Exactement le même. Vous venez de réimplémenter (en miniature) le cœur de deux des logiciels les plus utilisés au monde. Ça ne vous donne pas le droit de frimer en soirée, mais presque. (Enfin, si, un peu. « J'ai écrit une bibliothèque de calcul formel en LISP. » — effet garanti sur les informaticiens, nul sur les autres.)

Et maintenant ? On a des outils.

Voilà où on en est dans notre voyage C → LISP → outil concret :

🔧 C (vm.c, let.c)
λ Évaluateur LISP
📚 Bibliothèques LISP
🚀 Vous êtes ici

On a commencé avec du C pur : une VM, un parseur, un évaluateur. On a écrit du LISP : des fonctions récursives qui manipulent des arbres. On a fini avec des outils : une bibliothèque de calcul formel et une bibliothèque de calcul numérique. Le tout avec le même binaire ./let, sans recompiler, sans installer de package.

C'est ça, l'idée : le socle est en C, la créativité est en LISP. Le C te donne la machine. Le LISP te donne le volant. Et maintenant, tu peux conduire.

🗺️ En résumé : notre projet contient maintenant trois couches : Pas besoin de package manager. Pas besoin de pip install. Juste ./let '(load matlab.lisp)' et c'est parti. L'outil est dans le langage, le langage est dans l'outil. Ouroboros informatique.
🐍 « Mais Python a des bindings pour tout ! » — oui, et c'est génial. Mais Python, c'est 20 ans de développement et des milliers de contributeurs. Nous, on a fait ça en un semestre, à trois, avec du C et une idée. Le génie, c'est 1% d'inspiration et 99% de transpiration. (Ok, la blague était nulle. Mais elle est restée.)