Esplora come l'approccio simbolico di John McCarthy e le idee di Lisp—liste, ricorsione e garbage collection—hanno influenzato l'AI e la programmazione moderna.

Questa non è una visita al museo dell’“AI vecchia scuola”. È una lezione di storia pratica per chi costruisce software—programmatori, tech lead e product builder—perché le idee di John McCarthy hanno plasmato il modo in cui pensiamo a cosa servono i linguaggi di programmazione.
Lisp non era solo una nuova sintassi. Era una scommessa: il software poteva manipolare idee (non solo numeri) e le scelte di progettazione del linguaggio potevano accelerare ricerca, iterazione di prodotto e interi ecosistemi di tool.
Un modo utile per leggere l’eredità di McCarthy è come una domanda ancora rilevante oggi: quanto direttamente possiamo trasformare l’intento in un sistema eseguibile—senza affogarci in boilerplate, attrito o complessità accidentale? Quella domanda riecheggia dal REPL di Lisp fino ai moderni workflow “chat-to-app”.
John McCarthy è ricordato non solo per aver contribuito a lanciare l’AI come campo di ricerca, ma per aver insistito su un tipo specifico di AI: sistemi che potessero manipolare idee, non solo calcolare risposte. A metà degli anni ’50 organizzò il Dartmouth Summer Research Project (dove fu proposto il termine “artificial intelligence”) e poi influenzò il lavoro sull’AI al MIT e successivamente a Stanford. Ma il suo contributo più duraturo potrebbe essere la domanda che continuava a porre: e se il ragionamento stesso potesse essere espresso come un programma?
I primi successi del calcolo erano per lo più numerici: tabelle balistiche, simulazioni ingegneristiche, ottimizzazione e statistica. Questi problemi si prestavano bene all’aritmetica.
McCarthy mirava a qualcosa di diverso. Il ragionamento umano spesso lavora con concetti come “se”, “perché”, “appartiene a”, “è un tipo di” e “tutte le cose che soddisfano queste condizioni”. Queste non si rappresentano naturalmente con valori floating-point.
L’approccio di McCarthy trattava la conoscenza come simboli (nomi, relazioni, categorie) e considerava il pensiero come trasformazioni basate su regole su quei simboli.
In alto livello: gli approcci numerici rispondono a “quanto?” mentre gli approcci simbolici cercano di rispondere a “cos’è?” e “cosa segue da ciò che sappiamo?”.
Se credi che il ragionamento possa essere reso programmabile, ti serve un linguaggio che rappresenti comodamente espressioni come regole, affermazioni logiche e relazioni annidate—e poi le processi.
Lisp fu costruito per questo scopo. Invece di forzare le idee in strutture dati rigide e prefatte, Lisp rendeva naturale rappresentare codice e conoscenza in forma simile. Questa scelta non era di stile accademico: era un ponte pratico tra descrivere un pensiero ed eseguire una procedura, proprio il ponte che McCarthy voleva che l’AI attraversasse.
Quando McCarthy e i primi ricercatori di AI dicevano “simbolico”, non intendevano matematica misteriosa. Un simbolo è semplicemente un'etichetta significativa: un nome come customer, una parola come hungry o un tag come IF e THEN. I simboli contano perché permettono a un programma di lavorare con idee (categorie, relazioni, regole) anziché solo numeri grezzi.
Un modo semplice per immaginarlo: i fogli di calcolo sono ottimi quando il tuo mondo è colonne e aritmetica. I sistemi simbolici sono ottimi quando il tuo mondo è fatto di regole, categorie, eccezioni e struttura.
In molti programmi, la differenza tra 42 e "age" non è il tipo di dato—è ciò che il valore rappresenta. Un simbolo ti dà qualcosa da confrontare, memorizzare e combinare senza perdere significato.
Questo rende naturale rappresentare affermazioni come “Parigi è una città” o “se la batteria è scarica, trova un caricabatterie”.
Per fare qualcosa di utile con i simboli servono strutture. Lisp ha reso popolare una struttura molto semplice: la lista. Una lista è solo un gruppo ordinato di elementi, e quegli elementi possono essere a loro volta liste. Con quell’idea puoi rappresentare frasi, forme e conoscenza ad albero.
Ecco un piccolo esempio concettuale (in stile Lisp):
(sentence (subject robot) (verb needs) (object power))
Si legge quasi come l’inglese: una frase composta da soggetto, verbo e oggetto. Essendo strutturata, un programma può estrarre (subject robot) o sostituire (object power) con qualcos’altro.
Quando l’informazione è in strutture simboliche, i classici compiti di AI diventano affrontabili:
La svolta è che il programma non si limita a calcolare; manipola pezzi di conoscenza significativi in una forma che può ispezionare e trasformare.
Le decisioni progettuali di Lisp non rimasero solo in ambito accademico. Hanno influenzato come si costruiscono gli strumenti e la velocità con cui si esplorano le idee:
Queste caratteristiche danno origine a ecosistemi dove sperimentare è poco costoso, i prototipi diventano prodotti più in fretta e i team si adattano quando i requisiti cambiano.
Lisp nacque da un problema pratico: come scrivere programmi che possano lavorare con simboli con la stessa naturalezza con cui lavorano con i numeri?
McCarthy non voleva costruire un “calcolatore migliore”. Voleva un linguaggio dove un’espressione come (is (parent Alice Bob)) potesse essere memorizzata, ispezionata, trasformata e ragionata con la stessa facilità di (+ 2 3).
La priorità era rendere facile rappresentare e manipolare informazioni simboliche. Questo portò a concentrarsi su liste e strutture ad albero, perché si mappano bene a ciò che gli umani usano per esprimere significato: frasi, regole logiche, categorie nidificate e relazioni.
Un altro obiettivo era mantenere il nucleo del linguaggio piccolo e coerente. Quando un linguaggio ha meno “casi speciali”, passi meno tempo a memorizzare regole e più tempo a comporre idee. Lisp puntava su un piccolo set di mattoni fondamentali combinabili in astrazioni più grandi.
Un’intuizione chiave fu che programmi e dati possono condividere la stessa struttura. In parole povere: se i tuoi dati sono liste annidate, anche il tuo programma può essere una lista annidata.
Questo significa che puoi:
Lisp ha anche diffuso una mentalità: i linguaggi non devono essere monouso. Possono essere progettati attorno a un dominio—come ragionamento, ricerca e rappresentazione della conoscenza—e influenzare comunque la programmazione general-purpose per decenni.
Le S-expressions (abbreviazione di symbolic expressions) sono l’idea distintiva di Lisp: un modo singolo e coerente per rappresentare codice e dati come liste annidate.
A prima vista, una S-expression è solo parentesi intorno a elementi—alcuni elementi sono atomi (nomi e numeri), altri sono a loro volta liste. La regola “liste dentro liste” è tutto il punto.
Poiché la struttura è uniforme, i programmi Lisp sono costruiti con gli stessi mattoni a tutti i livelli. Una chiamata di funzione, un pezzo di dati simile a una configurazione e un costrutto del programma possono tutti essere espressi come lista.
Questa coerenza paga subito:
Anche se non scrivi mai Lisp, questa è una lezione importante: quando un sistema è costruito da una o due forme prevedibili, passi meno tempo a combattere casi limite e più tempo a costruire.
Le S-expressions incoraggiano la composizione perché pezzi piccoli e leggibili si combinano naturalmente in altri più grandi. Quando il tuo programma è “solo liste annidate”, combinare idee spesso significa nidificare un’espressione dentro un’altra o assemblare liste da parti riusabili.
Questo ti spinge verso uno stile modulare: scrivi operazioni piccole che fanno una cosa, poi le incastri per esprimere un intento più ampio.
Lo svantaggio ovvio è la stranezza per i neofiti. Per molti la sintassi piena di parentesi sembra insolita.
Ma il vantaggio è la prevedibilità: una volta capite le regole di nidificazione, puoi vedere in modo affidabile la struttura di un programma—e anche gli strumenti possono farlo. Questa chiarezza è una grande ragione per cui le S-expressions hanno avuto conseguenze ben oltre Lisp.
La ricorsione si capisce facilmente con una metafora quotidiana: pulire una stanza disordinata facendo stanze più piccole. Non tenti di risolvere tutto in una volta. Prendi un oggetto, lo metti dove deve stare, poi ripeti la stessa azione su ciò che rimane. I passi sono semplici; la forza viene dalla ripetizione fino a quando non resta più nulla da fare.
Lisp punta su questa idea perché gran parte dei suoi dati è naturalmente costruita da liste: una lista ha una “prima cosa” e “il resto”. Quella forma si adatta perfettamente al pensiero ricorsivo.
Per processare una lista, gestisci il primo elemento e poi applichi la stessa logica al resto. Quando la lista è vuota, ti fermi—questo è il momento “non c’è più nulla da fare” che rende la ricorsione ben definita anziché misteriosa.
Immagina di voler ottenere il totale di una lista di numeri.
Questo è tutto. La definizione si legge come l’inglese, e la struttura del programma rispecchia l’idea.
L’AI simbolica spesso rappresenta espressioni come strutture ad albero (un operatore con sotto-espressioni). La ricorsione è un modo naturale per “camminare” quell’albero: valuta la parte sinistra nello stesso modo in cui valuti la destra e continua finché non raggiungi un valore semplice.
Questi pattern hanno contribuito a plasmare la programmazione funzionale successiva: funzioni piccole, casi base chiari e trasformazioni di dati facili da ragionare. Anche fuori da Lisp, l’abitudine di spezzare il lavoro in “fai un passo, poi ripeti sul resto” porta a programmi più puliti e con meno effetti collaterali nascosti.
I primi programmatori spesso dovevano gestire la memoria manualmente: allocare spazio, tracciare chi “possiede” l’oggetto e ricordarsi di liberarlo al momento giusto. Questo lavoro non rallenta solo lo sviluppo: genera una classe di bug difficili da riprodurre e facili da spedire: perdite che degradano le prestazioni e puntatori pendenti che fanno crashare il programma molto dopo l’errore originale.
John McCarthy introdusse la garbage collection per Lisp come modo per permettere ai programmatori di concentrarsi sul significato invece che sulla contabilità.
A grandi linee, la garbage collection (GC) trova automaticamente pezzi di memoria non più raggiungibili dal programma in esecuzione—valori che nulla potrà più usare—e recupera quello spazio.
Invece di chiederti “abbiamo liberato ogni oggetto esattamente una volta?”, la GC sposta la domanda su “questo oggetto è ancora accessibile?”. Se il programma non può raggiungerlo, è considerato spazzatura.
Per il lavoro di AI simbolica, i programmi Lisp creano frequentemente molte liste, alberi e risultati intermedi a vita breve. La gestione manuale della memoria trasformerebbe la sperimentazione in una battaglia costante con la pulizia delle risorse.
La GC cambia l’esperienza quotidiana:
L’idea chiave è che una caratteristica del linguaggio può essere un moltiplicatore per il team: meno ore spese a debug di corruzione misteriosa significa più tempo per migliorare la logica.
La scelta di McCarthy non rimase confinata a Lisp. Molti sistemi successivi adottarono la GC (e sue varianti) perché il compromesso spesso ripaga: Java, C#, Python, runtime JavaScript e Go si affidano alla garbage collection per rendere lo sviluppo su larga scala più sicuro e veloce—anche quando le prestazioni sono importanti.
In Lisp, un espressione è un pezzo di codice scritto in una forma coerente (spesso una lista). La valutazione è semplicemente il processo di decidere cosa quell’espressione significa e cosa produce.
Per esempio, quando scrivi qualcosa come “somma questi numeri” o “chiama questa funzione con questi input”, l’evaluatore segue un piccolo set di regole per trasformare quell’espressione in un risultato. Pensalo come l’arbitro del linguaggio: decide cosa fare dopo, in quale ordine e quando fermarsi.
La mossa chiave di McCarthy non fu solo inventare una nuova sintassi—fu mantenere il “motore di significato” compatto e regolare. Quando l’evaluatore è costruito su poche regole chiare, succedono due cose buone:
Questa coerenza è una delle ragioni per cui Lisp divenne un campo di prova per idee nell’AI simbolica: i ricercatori potevano provare nuove rappresentazioni e controlli rapidamente, senza aspettare che un team di compilazione riprogettasse il linguaggio.
Le macro sono il modo di Lisp per automatizzare forme di codice ripetitive, non solo valori ripetuti. Dove una funzione ti aiuta a evitare di ripetere calcoli, una macro ti aiuta a evitare di ripetere strutture comuni—pattern come “fai X, ma registra anche”, o “definisci una mini-lingua per regole”.
L’effetto pratico è che Lisp può far crescere nuove comodità dall’interno. Molti strumenti moderni richiamano quest’idea—sistemi di template, generatori di codice e funzionalità di metaprogrammazione—perché supportano lo stesso obiettivo: sperimentazione più rapida e intenzione più chiara.
Se ti interessa come questa mentalità ha influenzato i workflow quotidiani, vedi /blog/the-repl-and-fast-feedback-loops.
Una grande parte dell’attrattiva di Lisp non era solo il linguaggio—era il modo in cui ci lavoravi. Lisp diffuse il REPL: Read–Eval–Print Loop. In termini pratici, è come avere una conversazione con il computer. Scrivi un’espressione, il sistema la esegue immediatamente, stampa il risultato e aspetta il tuo prossimo input.
Invece di scrivere un intero programma, compilarlo, eseguirlo e poi cercare cosa è andato storto, puoi provare idee un piccolo passo alla volta. Definisci una funzione, testala con pochi input, aggiustala e ritestala—tutto in pochi secondi.
Queto ritmo incoraggia la sperimentazione, cosa cruciale nell’AI primitiva dove spesso non si conosceva l’approccio giusto a priori.
Il feedback rapido trasforma “scommesse grandi” in “controlli piccoli”. Per la ricerca, rende più facile esplorare ipotesi e ispezionare risultati intermedi.
Per il prototyping di prodotto, riduce il costo dell’iterazione: puoi validare il comportamento con dati reali rapidamente, notare casi limite prima e affinare le funzionalità senza lunghe build.
Questo è anche il motivo per cui gli strumenti vibe-coding moderni sono interessanti: comprimono i cicli di feedback. Per esempio, Koder.ai usa un’interfaccia chat (con un’architettura agent-based sotto il cofano) per trasformare l’intento di prodotto in codice web, backend o mobile funzionante—spesso facendo sembrare il ciclo “prova → aggiusta → riprova” più vicino a un REPL che a una pipeline tradizionale.
L’idea del REPL si ritrova oggi in:
Strumenti diversi, stesso principio: accorciare la distanza tra pensare e vedere.
I team traggono più beneficio dai workflow tipo REPL quando esplorano requisiti incerti, costruiscono funzionalità data-heavy, progettano API o debugano logiche complesse. Se il lavoro implica apprendimento rapido—su utenti, dati o casi limite—lo sviluppo interattivo non è un lusso; è un moltiplicatore.
Lisp non ha “vinto” diventando la sintassi quotidiana di tutti. Ha vinto seminando idee che sono diventate normali in molti ecosistemi.
Concetti che Lisp considerava predefiniti—funzioni come valori, uso estensivo di operazioni higher-order e preferenza per costruire programmi componendo piccole parti—appaiono ormai ovunque. Anche in linguaggi che non somigliano a Lisp si incoraggiano trasformazioni map/filter, abitudini di dati immutabili e pensiero ricorsivo (spesso espresso con iteratori o fold).
Lo spostamento mentale è: tratta le trasformazioni di dati come pipeline e il comportamento come qualcosa che puoi passare in giro.
Lisp rese i programmi facili da rappresentare come dati. Quella mentalità si vede oggi nel modo in cui costruiamo e manipoliamo AST (abstract syntax trees) per compilatori, formatter e generatori di codice. Quando lavori con un AST stai facendo una stretta cugina di “codice come dati”, anche se le strutture sono oggetti JSON, nodi tipizzati o grafi di bytecode.
Lo stesso approccio simbolico alimenta l’automazione pratica: formati di configurazione, sistemi di template e pipeline di build si basano su rappresentazioni strutturate che gli strumenti possono ispezionare, trasformare e validare.
I linguaggi della famiglia Lisp (in senso ampio: Lisp contemporanei e strumenti ispirati a Lisp) continuano a influenzare il design di DSL interni—mini-linguaggi focalizzati per test, deployment, data wrangling o UI.
Fuori da Lisp, sistemi di macro, librerie di metaprogrammazione e framework di codegen mirano allo stesso risultato: estendere il linguaggio per adattarlo al problema.
Una lezione pragmatica: le preferenze di sintassi cambiano, ma le idee durevoli—struttura simbolica, funzioni componibili ed estendibilità—continuano a dare valore attraverso decenni e codebase.
Lisp ha una reputazione che oscilla tra “geniale” e “illeggibile”, spesso basata su impressioni di seconda mano anziché sull’esperienza quotidiana. La verità è più ordinaria: Lisp prende decisioni che sono potenti nel contesto giusto e scomode in altri.
Per i nuovi arrivati, la sintassi uniforme di Lisp può sembrare di guardare l’“interno” di un programma più che la sua superficie levigata. Questa sensazione è reale, specialmente se sei abituato a linguaggi in cui la sintassi separa visivamente costrutti diversi.
Storicamente, però, la struttura di Lisp è anche il punto: codice e dati condividono la stessa forma, il che rende i programmi più facili da trasformare, generare e analizzare. Con un buon supporto dell’editor (indentazione, navigazione strutturale), il codice Lisp si legge spesso più per forma che contando parentesi.
Uno stereotipo comune è che Lisp sia intrinsecamente lento. Storicamente, alcune implementazioni hanno sofferto rispetto a linguaggi di basso livello, e le caratteristiche dinamiche possono aggiungere overhead.
Ma non è corretto trattare “Lisp” come un profilo di prestazioni unico. Molti sistemi Lisp supportano compilazione, dichiarazioni di tipo e ottimizzazioni serie. Il quadro più utile è: quanto controllo ti serve sul layout della memoria, sulla latenza prevedibile o sul throughput grezzo—e la tua implementazione Lisp mira a questi obiettivi?
Un’altra critica ragionevole è l’adeguatezza dell’ecosistema. A seconda del dialetto Lisp e del dominio, librerie, tooling e pool di talenti possono essere più piccoli rispetto agli stack mainstream. Questo può pesare più dell’eleganza del linguaggio se devi spedire rapidamente con un team ampio.
Invece di giudicare Lisp dalle sue stereotipie, valuta le sue idee di base: struttura uniforme, sviluppo interattivo e macro come strumento per costruire astrazioni di dominio. Anche se non distribuisci mai un sistema Lisp, quei concetti possono affinare il modo in cui pensi al design dei linguaggi e a come scrivi codice in qualsiasi ambiente.
McCarthy non ci ha lasciato solo un linguaggio storico—ci ha lasciato un insieme di abitudini che rendono ancora il software più facile da cambiare, spiegare ed estendere.
Preferisci nuclei semplici a superfici ingegnose. Un piccolo set di mattoni ortogonali è più facile da imparare e più difficile da rompere.
Mantieni uniformi le forme dei dati. Quando molte cose condividono la stessa rappresentazione (liste/alberi), gli strumenti diventano più semplici: printer, debugger, serializer e transformer possono essere riutilizzati.
Tratta i programmi come dati (e i dati come programmi) quando aiuta. Se puoi ispezionare e trasformare strutture, puoi realizzare refactor più sicuri, migrazioni e generatori di codice.
Automatizza il lavoro noioso. La garbage collection è l’esempio classico, ma il punto più ampio è: investi in automazioni che prevengano intere classi di errori.
Ottimizza per i cicli di feedback. La valutazione interattiva (stile REPL) incoraggia piccoli esperimenti, verifica rapida e migliore intuizione sul comportamento.
Fai dell’estendibilità un obiettivo primario. Le macro di Lisp sono una risposta; in altri ecosistemi questo può essere plugin, template, DSL o trasformazioni a compile-time.
Prima di scegliere una libreria o un’architettura, prendi una funzionalità reale—per esempio “regole sconto” o “instradamento ticket di supporto”—e disegnala come albero. Poi riscrivilo come liste annidate (o JSON). Chiediti: quali sono i nodi, quali le foglie e quali trasformazioni servono?
Anche senza Lisp, puoi adottare la mentalità: costruisci rappresentazioni tipo AST, usa la generazione di codice per il glue ripetitivo e standardizza pipeline data-first (parse → transform → evaluate). Molti team ottengono benefici semplicemente rendendo esplicite le rappresentazioni intermedie.
Se ti piace il principio REPL ma distribuisci feature in stack mainstream, puoi comunque riprendere lo spirito negli strumenti: cicli di iterazione ridotti, snapshot/rollback ed esplicita pianificazione prima dell’esecuzione. Koder.ai, per esempio, include una modalità di pianificazione oltre a snapshot e rollback per mantenere l’iterazione rapida più sicura—un’eco operativa del “cambia in fretta, ma resta in controllo” di Lisp.
L’influenza duratura di McCarthy è questa: la programmazione diventa più potente quando rendiamo il ragionamento stesso programmabile—e manteniamo il percorso dall’idea al sistema eseguibile il più diretto possibile.
Il pensiero simbolico rappresenta concetti e relazioni direttamente (per esempio: “customer”, “is-a”, “depends-on”, “if…then…”), e applica regole e trasformazioni a quelle rappresentazioni.
È più utile quando il problema è pieno di struttura, eccezioni e significato (motori di regole, pianificazione, compilatori, configurazioni, logiche di workflow), non solo aritmetica.
McCarthy sostenne l'idea che il ragionamento potesse essere espresso come programmi—non solo calcoli.
Questa prospettiva ha influenzato:
Le liste sono un modo minimale e flessibile per rappresentare “cose fatte di parti”. Poiché gli elementi di una lista possono essere a loro volta liste, si ottengono naturalmente strutture ad albero.
Questo rende semplice:
Le S-expressions ti danno una forma uniforme per codice e dati: liste annidate.
Questa uniformità semplifica i sistemi perché:
Una macro automatizza forme di codice ripetitive, non solo calcoli ripetuti.
Usa le macro quando vuoi:
Se ti serve solo logica riutilizzabile, una funzione è quasi sempre la scelta migliore.
La garbage collection (GC) recupera automaticamente la memoria non più raggiungibile dal programma, riducendo intere categorie di bug (puntatori pendenti, doppi free).
È particolarmente utile quando il programma crea molte strutture a vita breve (liste/alberi/AST), perché permette di prototipare e rifattorizzare senza progettare subito uno schema di proprietà della memoria.
Un REPL accorcia il ciclo “pensare → provare → osservare”. Puoi definire una funzione, eseguirla, modificarla e rieseguirla immediatamente.
Per ottenere benefici simili in ambienti non Lisp:
Lettura correlata: /blog/the-repl-and-fast-feedback-loops
Molti flussi di lavoro moderni riutilizzano le stesse idee di base:
map/filter, composizione)Anche se non distribuisci Lisp, probabilmente usi quotidianamente abitudini derivate da Lisp.
I compromis principali sono:
L'approccio pratico è valutare l'idoneità in base al dominio e ai vincoli, non alla reputazione.
Prova questo esercizio di 10 minuti:
Questo spesso mette in luce dove i pattern “codice-come-dati”, motori di regole o config simili a DSL semplificherebbero il sistema.