<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Elia Scotto</title><description>Essays, reviews, and tech articles</description><link>https://scotto.me/</link><item><title>Junky - William S. Burroughs</title><link>https://scotto.me/blog/2026-01-23-junky-william-s-burroughs/</link><guid isPermaLink="true">https://scotto.me/blog/2026-01-23-junky-william-s-burroughs/</guid><description>Confessions of an Unredeemed Drug Addict</description><pubDate>Fri, 23 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Junky è un libro affascinante che tratta di un tema singolare in letteratura; la vita di un dipendente dall’eroina. William Burroughs decide di raccontare in prima persona — nonostante usi uno pseudonimo — perché lui stesso è stato dipendente dalla sostanza per quasi tutta la vita. Junky è stato il suo primo successo, ed ha elevato Burroughs a capostipite della beat generation, la corrente letteraria americana di metà secolo scorso, basata su una forte ribellione artistica e sessuale accompagnata da un consumo smoderato di droghe.&lt;/p&gt;
&lt;p&gt;La storia si svolge a metà degli anni 40 in America, ed è di fatto la biografia della dipendenza di Burroughs, anche se i fatti narrati non sono tutti totalmente reali. Cosa passa nella mente di un tossico? Come pensa? Qual’è il vocabolario che usa e quali sono le sue unità di misura? Il libro è fenomenale nel raccontare la quotidianità di un eroinomane, sempre alla ricerca della sostanza, o di soldi, sempre a fare attenzione di non esser “pizzicato” dalla polizia. Più che una successione di eventi, il racconto è un viaggio interiore. Le pagine contengono i pensieri quotidiani di un tossicodipendente accompagnati dalla cronaca minuziosa dei suoi frequenti abusi, delle astinenze e le successive ricadute.&lt;/p&gt;
&lt;p&gt;Junky è un viaggio scandito da continui spostamenti tra città e nazioni, per sfuggire a possibili arresti, da frequenti visite ai dottori per ottenere prescrizioni per la morfina, dallo spaccio per guadagnare soldi e poter mantenere la dipendenza, ma soprattutto dal costante bisogno quotidiano di eroina. Dalle pagine traspare il vero significato della dipendenza, che prima che esser fisica è soprattutto mentale. Il pensiero di trovare la sostanza diventa il motore sia della storia che della vita di chi, come Burroughs, ne è schiavo. Tutte le pagine non parlano d’altro che di eroina, trasformando il racconto nel diario intimo di un tossico.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/burroughs.jpg&quot; alt=&quot;William S. Burroughs&quot; title=&quot;William S. Burroughs&quot;&gt;&lt;/p&gt;
&lt;p&gt;La dipendenza viene continuamente interrotta. Una volta al protagonista manca l’eroina, un altra volta sceglie di disintossicarsi e si rivolge a una clinica, un altra ancora finisce in cella. Ogni volta è scabroso il racconto dei dolori fisici che prova quando al suo corpo viene a mancare la dose. Parliamo di febbre, nausea, insonnia, diarrea, dolori diffusi, soprattutto alla spina dorsale, quest’ultimo chiamato in gergo “la scimmia sulla schiena” — uno dei titoli alternativi con cui in Italia è uscito il romanzo. Sono paragrafi raccapriccianti e snervanti, dove spesso il protagonista è costretto ad assumere dosi di altre sostanze per calmare i dolori e compensare lo sbilanciamento chimico. Quando Burroughs si trova in astinenza si rivolge ad altre sostanze, principalmente alcool e erba, ma anche oppiacei vari, cocaina, barbiturici, addirittura prova il peyote messicano, per riuscire a calmare il bisogno di eroina. La cosa più triste di tutte sono le continue ricadute, che perdurano fino alla fine del romanzo. Burroughs non riesce mai a smettere per più di un paio di mesi, che incontra qualcuno che gli offre una nuova dose. Lui non riesce a trattenersi, si fa, e in un attimo si ritrova intrappolato nel solito ciclo distruttivo.&lt;/p&gt;
&lt;p&gt;I personaggi nella storia appaiono e scompaiono. Di solito sono clienti o amici che coprono un ruolo per la durata di qualche pagina, poi Burroughs si sposta, cambia città, finisce in cella, e tutto ricomincia da capo con nuovi personaggi, nuovi prezzi, ma identiche sostanze. Le persone più vicine a lui, come la moglie, vengono menzionate raramente, quasi fossero degli intoppi insignificanti davanti all’enorme soddisfazione che prova quando si inietta l’eroina. Eppure la moglie non esita a definirlo noioso e patetico quando intuisce che sta per ricadere nella dipendenza.&lt;/p&gt;
&lt;p&gt;Infinte Burroughs nel romanzo non critica direttamente la droga, anzi promuove l’eroina a stile di vita fin dall’introduzione. Nel libro non incoraggia il lettore a farne uso, anzi sembra quasi voglia ridimensionare la propria dipendenza. Parla spesso di vari parametri, come il numero di mesi e di iniezioni quotidiane, quasi a dire che la sua non è una dipendenza tale da dover esser ritenuta grave — una tipica auto-riflessione da tossico. La critica all’eroina fuoriesce dal racconto. Non si può che provare pena per i dolori che soffre il protagonista quando è in astinenza, per la continua lotta nella ricerca di una nuova dose, per il terrore costante di essere scoperto e incarcerato per spaccio, ma più di tutto per la continua ossessione per la droga che occupa la sua mente. Per un junky l&amp;#39;intera vita ruota attorno all&amp;#39;eroina: persino il risveglio viene dominato dal bisogno della dose, senza la quale la normalità resta irraggiungibile.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The question is frequently asked: Why does a man become a drug addict?&lt;br&gt;The answer is that he usually does not intend to become an addict. You don’t wake up one morning and decide to be a drug addict. It takes at least three months’ shooting twice a day to get any habit at all. And you don’t really know what junk sickness is until you have had several habits. It took me almost six months to get my first habit, and then the withdrawal symptoms were mild. I think it no exaggeration to say it takes about a year and several hundred injections to make an addict.&lt;br&gt;The questions, of course, could be asked: Why did you ever try narcotics? Why did you continue using it long enough to become an addict? You become a narcotics addict because you do not have strong motivations in the other direction. Junk wins by default. I tried it as a matter of curiosity. I drifted along taking shots when I could score. I ended up hooked. Most addicts I have talked to report a similar experience. They did not start using drugs for any reason they can remember. They just drifted along until they got hooked. If you have never been addicted, you can have no clear idea what it means to need junk with the addict’s special need. You don’t decide to be an addict. One morning you wake up sick and you’re an addict.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Liture, How I Manage My Reading Notes</title><link>https://scotto.me/blog/2025-12-17-liture-how-i-manage-my-reading-notes/</link><guid isPermaLink="true">https://scotto.me/blog/2025-12-17-liture-how-i-manage-my-reading-notes/</guid><description>On preserving what we write in the margins of ebooks</description><pubDate>Wed, 17 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Useful software often comes out of frustration. When a programmer finds themselves wondering about a tool or a library that doesn’t exist yet, they may decide to write it. At least, they know it would be useful for one user, themself, not to mention the fun of coding. In my case, I needed an easy way to navigate my ebook notes, and all I could find were subscription based services that didn’t fit my casual use. This is how &lt;a href=&quot;https://liture.co/&quot;&gt;&lt;strong&gt;Liture&lt;/strong&gt;&lt;/a&gt; started.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Readers eat books. Film eats viewers.”
— &lt;em&gt;The Wave in the Mind&lt;/em&gt;, Ursula K. Le Guin&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the advantages of physical books is that we can write on the margins. That text filling the white space that the page offers has a name, marginalia, and it’s the old analog way to make that book copy feel ours. By writing on it we can enrich the text with our thoughts, and also highlight our favourite passages as well as the most crucial one to help us revise our reading later on. I tend to use a pencil, a ruler, and some coloured tags attached in the margin for quickly jumping to the best parts in the future. &lt;/p&gt;
&lt;p&gt;A lot of great writers in the past have used marginalia. Some of their book copies are still preserved, online we can see photos of the annotations made by names like Herman Melville, William Blake, Mark Twain, Jane Austen, Sylvia Plath, Jack Kerouac. I know that Vladimir Nabokov used to draw maps and object, since some of the original pages were printed on his posthumous book &lt;a href=&quot;https://www.goodreads.com/book/show/51169646-lectures-by-vladimir-nabokov&quot;&gt;&lt;em&gt;Lectures on Literature&lt;/em&gt;&lt;/a&gt;, like his annotated copy of Franz Kafka’s Metamorphosis. David Foster Wallace became famous for his heavy use of margin annotations. Some of his copies of Cormac McCarthy and Don DeLillo, filled with various notes, numbers and drawings, are kept exposed at the University of Texas in Austin.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/nabokov-annot.jpg&quot; alt=&quot;A page from Franz Kafka’s Metamorphosis annotated by Nabokov&quot; title=&quot;Nabokov copy of Franz Kafka’s Metamorphosis&quot;&gt;&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve read far more books recently than I did a few years ago, and I do most of my readings with an ebook reader. I love printed books. I miss the feeling that touching paper gives, the visual feedback of the progress, the different style for covers and typography used for each book. I still buy physical copies and often second-hand, but I prefer to read in Italian[^1], my original language, and since I&amp;#39;m in Australia, far from Italian bookshops and I don’t want to pollute the planet with my hobbies, I mostly read digital books.&lt;/p&gt;
&lt;p&gt;I also have the habit of highlighting in books and sometimes taking annotations, which it&amp;#39;s easy using an e-reader.  Soon my collection of quotes and notes had grown. For some books, like &lt;a href=&quot;https://www.goodreads.com/book/show/38820046-21-lessons-for-the-21st-century&quot;&gt;&lt;em&gt;21 Lessons for the 21st Century&lt;/em&gt;&lt;/a&gt;, I had to print my highlights to review them, filling around 15 pages, and then re-highlight the most important parts. Even if I find paper more effective for focus reading than digital text, I become frustrated every time I had to review my highlights and notes from an ebook, since I couldn&amp;#39;t find a way to access that data. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/wallace_books_delillo_002_large.jpg&quot; alt=&quot;David Foster Wallace annotations on Don Delillo&apos;s Players&quot; title=&quot;David Foster Wallace annotations on Don Delillo&amp;#39;s Players&quot;&gt;&lt;/p&gt;
&lt;p&gt;I use a Kobo reader and all the content that the user generates is stored inside the device. Export is not officially supported. A few years ago I started maintaining &lt;a href=&quot;%5Bhttps://github.com/eliascotto/export-kobo%5D(https://github.com/eliascotto/export-kobo)&quot;&gt;a python script&lt;/a&gt; that extracts highlights and notes in different formats, but with a lot of notes it stopped being practical. I needed a way to &lt;strong&gt;search&lt;/strong&gt; my &lt;em&gt;highlights&lt;/em&gt; and &lt;em&gt;notes&lt;/em&gt;, to &lt;strong&gt;save&lt;/strong&gt; my favourite quotes, to see them grouped by author and book. Also highlighting as well as typing on an e-reader is not that precise, the devices are usually quite slow to respond, so I ended up having a lot of errors and imperfection to fix. I want to edit my content, and also add new notes to enrich it. I needed a new kind of software for this.&lt;/p&gt;
&lt;p&gt;Annotations on ebooks should mimic those on physical books; they have to be easily accessible and preserved. Keeping them stored inside devices in proprietary formats is dangerous. They can easily get lost. I started thinking that some of my book notes matter as much as photos of my past, they’re a part of my reading experience and I want to keep them for the future.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/liture_screen.png&quot; alt=&quot;A screenshot of Liture desktop app&quot; title=&quot;A screenshot of Liture&quot;&gt;&lt;/p&gt;
&lt;p&gt;Kobo, Kindle, Apple Books, none of them facilitates exporting highlights and notes outside of their own platform, no common format exists for that. That’s where &lt;a href=&quot;https://liture.co/&quot;&gt;&lt;strong&gt;Liture&lt;/strong&gt;&lt;/a&gt; comes helpful. It’s a desktop app to import and manage notes and highlights from the main three book platforms. The user can search and edit content, save the favourite items, and also manually add new authors, books, and notes. No cloud or subscriptions, free for all platforms. I’m using it to keep importing new highlights every time I sync my Kobo on my mac, and the content is automatically stored in a SQLite file that I regularly backup.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“There must be something in books, something we can&amp;#39;t imagine, to make a woman stay in a burning house; there must be something there. You don&amp;#39;t stay for nothing.”
— &lt;em&gt;Fahrenheit 451&lt;/em&gt;, Ray Bradbury&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think there’s a lot of potential for great software for readers, and still a lot to do. Big tech companies disregard reading and prefer to focus on more lucrative content, so readers are usually left with old platforms, not frequently updated, missing modern features, and often with incompatible formats. But it&amp;#39;s not all lost. Some groups of passionate readers are awakening and trying to fix the situation building new platforms and apps. I would like to do my part releasing &lt;a href=&quot;https://github.com/eliascotto/liture-quotes&quot;&gt;Liture&lt;/a&gt;. Enjoy and make good use of it!&lt;/p&gt;
&lt;p&gt;[^1]: I tell others that I prefer Italian because I find the language generally more expressive than English, but in reality it’s because I fear I might forget it one day. So I read in Italian to keep the language from rusting in my head.&lt;/p&gt;
</content:encoded></item><item><title>Quel che resta del giorno - Kazuo Ishiguro</title><link>https://scotto.me/blog/2025-09-26-quel-che-resta-del-giorno/</link><guid isPermaLink="true">https://scotto.me/blog/2025-09-26-quel-che-resta-del-giorno/</guid><description>Cosa rimane quando svaniscono le illusioni?</description><pubDate>Fri, 26 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;La sera è ciò che resta del giorno, come la vecchiaia è ciò che ti resta della vita. Il protagonista dell’opera di Kazuo Ishiguro uscita nel 1989, è un maggiordomo oramai anziano che decide di intraprendere un viaggio, sia fisico guidando la sua auto nelle campagne del sud-ovest dell’Inghilterra, sia nella sua memoria, ripercorrendo scene della sua carriera presso la dimora Darlington. &lt;em&gt;Quel che resta del giorno&lt;/em&gt; è stato il primo successo dello scrittore inglese, grazie al quale vinse il &lt;em&gt;Booker Prize&lt;/em&gt; che gli permise di esser riconosciuto a livello internazionale, anche grazie alla trasposizione cinematografica con Anthony Hopkins come attore protagonista.&lt;/p&gt;
&lt;figure&gt;
  &lt;source type=&quot;image/avif&quot; srcset=&quot;/assets/img/2721.avif&quot; /&gt;
  &lt;img
    src=&quot;/assets/img/2721.png&quot;
    alt=&quot;Describe the image here&quot;
    loading=&quot;lazy&quot;
    decoding=&quot;async&quot;
  /&gt;
  &lt;figcaption&gt;Stevens interpretato da Anthony Hopkins nel 1993&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Stevens è un maggiordomo a capo del servizio nella residenza di un lord inglese chiamato Darlington. Dopo la morte del padrone, la residenza omonima passa in mano a un magnate americano Mr Faraday, che nonostante i tagli del personale, necessita di buoni servitori al suo servizio. Stevens decide quindi di prendersi qualche giorno di vacanza per andare a trovare una sua conoscenza che anni prima aveva servito a Darlington hall, Miss Kenton, e convincerla a riprendere il suo vecchio ruolo. Il viaggio in auto lo porta attraverso i panorami della campagna inglese dove si trova a riflettere in una serie di flashback che raccontano episodi della sua carriera con lord Darlington. In queste memorie si intrecciano più trame, come il contesto storico del racconto, descritto tramite la partecipazione politica del lord nelle fasi preliminari della seconda guerra mondiale e la sua decisione di appoggiare l’operato della Germania, oltre che vari avvenimenti che riguardano i personaggi che soggiornano a Darlington hall.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“La sera è la parte più bella della giornata. Hai concluso una giornata di lavoro e adesso puoi sederti ed essere felice. Ecco come la vedo io. Domandate a chiunque e vedrete che vi diranno tutti la stessa cosa. La sera è la parte più bella della giornata.”
— Stevens&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Il racconto è in prima persona dal punto di vista del maggiordomo, un espediente utile a Ishiguro per narrare vari avvenimenti, come le cerimonie formali tra rappresentanti politici di varie nazioni che avvengono nella residenza, attraverso gli occhi — e le orecchie — di un maggiordomo. Stevens riporta solamente porzioni di conversazioni, battute di personaggi e piccoli incidenti che gli sono rimasti nella memoria, i quali vengono lasciati al lettore come pezzi di un puzzle da comporre per comprendere ciò che non viene direttamente narrato. Il nascondere gli eventi e rivelarli solo in parte durante la narrazione è uno dei tratti caratteristici dello stile di Ishiguro, che ho ritrovato anche nel suo romanzo successivo &lt;em&gt;Non lasciarmi&lt;/em&gt;. Oltre a questa unicità, in entrambi i romanzi i protagonisti prendono parte a una storia d’amore che, sebbene ordinaria, è anche inconclusiva. Infatti anche in &lt;em&gt;Quel che resta del giorno&lt;/em&gt;, scopriamo che i sentimenti di Miss Kenton per Stevens, in qualche modo si intuiscono dai flashback ma vengono rivelati del tutto nel finale, vengono totalmente ignorati dal maggiordomo. Stevens è profondamente convinto che esista una precisa nozione di &lt;em&gt;grandezza&lt;/em&gt; riferita alla professione del maggiordomo. Nonostante la definizione non appaia mai univoca nel libro ma sempre frammentaria, si può ricostruire che essa consista nel reputare &lt;em&gt;onorevole&lt;/em&gt; il servire il proprio lord al meglio delle proprie possibilità, ripudiando qualsiasi comportamento inadatto o giudizio sugli avvenimenti nella residenza, oltre che mostrandosi sempre in atteggiamento sobrio e disponibile a qualsiasi richiesta. Stevens intende chiaramente dimostrare a se stesso di poter essere un grande maggiordomo, nonostante non si reputi tale, ed è per questo che decide di non dar corda ai sentimenti di Miss Kenton, sarebbero per lui solo una distrazione inopportuna.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets//img/1xt1.brty8d.940.jpg&quot; alt=&quot;Darlington Hall&quot; title=&quot;Darlington Hall&quot;&gt;&lt;/p&gt;
&lt;p&gt;Le capacità narrative di Ishiguro sono eccezionali. Per esempio in una scena, di per se secondaria ma che nel contesto della storia assume un’importanza centrale, Stevens sta leggendo un libro e Miss Kenton gli si avvicina cercando di rubarglielo di mano per scoprire cosa stesse leggendo. Ella vorrebbe fare con quel gesto una qualche breccia nella barriera professionale che ancora li divide e cercare di andare oltre la formalità dei loro discorsi, sempre diretti al servizio della casa e alle nuove disposizioni da attuare. Stevens si rifiuta fermamente, invitandola molto educatamente a non violare i suoi momenti di riposo. Miss Kenton infatti scopre giocosamente che Stevens stava leggendo una banale storia d’amore, ma il maggiordomo mette subito a freno la curiosità della domestica spiegandole che per adempire al meglio al suo ruolo è richiesto leggere, anche romanzi, per poter adottare sempre il vocabolario più adatto ad ogni conversazione. La scena evocativa mostra quanto Stevens sia capace di dedicarsi al suo ruolo, al punto da reprimere i suoi sentimenti e non distinguere più la sua vita privata dal lavoro.&lt;/p&gt;
&lt;p&gt;In entrambi i romanzi che ho letto dell’autore, mi sono ritrovato a pormi delle domande non banali. Esse emergono dalle pagine e non vengono mai poste direttamente, siccome i personaggi finiscono per ritrovarsi in situazioni ambigue e moralmente complesse. È una delle specialità dello stile narrativo di Kazuo Ishiguro. Mentre in &lt;em&gt;Non lasciarmi&lt;/em&gt; mi ero posto domande sui quali debbano essere i limiti della scienza, in questo libro è impossibile non domandarsi quali siano i limiti nella dedizione al proprio lavoro. Non si tratta di puro stacanovismo, ma piuttosto di chiedersi la fede incondizionata che ha Stevens per il proprio padrone, al punto da sopprimere qualsiasi dubbio, giudizio o addirittura sentimento perché in contrasto con i requisiti per essere un &lt;em&gt;grande&lt;/em&gt; maggiordomo, sia giustificata. Più di trent’anni dall’uscita del libro, è ovvio che quel tipo di mentalità è oramai antiquata, allo stesso modo di tutte le riflessioni che Stevens fa a favore di lasciare le decisioni fondamentali per il futuro del paese ad un gruppo di leader, perché a suo dire loro sanno quello che fanno meglio di chiunque altro. Da queste riflessioni emergono le differenze tra il vecchio modo di pensare di inizio secolo scorso, in cui si credeva che un uomo avesse un ruolo assegnatogli nella società e l’unica cosa che dovesse fare e svolgerlo al meglio, senza occuparsi di faccende complesse fuori dalla sua portata, in contrasto con l’uomo moderno, che da importanza all’educazione personale, e si sente libero sia di scegliere la propria strada nella vita, sia di poter esprimere opinioni e preferenze su eventi e decisioni che non lo riguardano direttamente. Miss Kenton nel racconto la pensa diversamente da Stevens, non crede che il suo ruolo definisca la sua intera vita. Sceglie l’unica via liberatoria che ha a disposizione, e decide di sposarsi, provando a costruirsi una famiglia, e smettendo così di essere alle dipendenze altrui. Anche se le cose alla fine non le sono andate per il meglio, ha scelto di esercitare la sua libertà provando a crearsi una vita diversa, rinunciando a dedicare tutta se stessa al ruolo di domestica. Stevens nel finale si rende conto di aver sprecato gran parte della sua vita inseguendo un ideale inconclusivo, e ha il rimpianto di non aver scelto per se un’altra strada per evitare la solitudine alla fine dei suoi giorni.&lt;/p&gt;
&lt;p&gt;Leggendo alcune interviste sul romanzo, Ishiguro ha dichiarato essersi documentato a dovere e di aver addirittura letto dei manuali inglesi sui maggiordomi di inizio secolo, che spiegavano le procedure corrette per organizzare il servizio e intrattenere gli ospiti. Dopo mesi di ricerca l’autore ha prodotto la prima bozza del romanzo in un solo mese, chiudendosi in nel suo studio a scrivere anche per tredici ore al giorno, per ridurre al minimo qualsiasi influenza esterna sulla sua immaginazione e concentrarsi solamente sulla scrittura della storia che aveva in mente. Durante questa immersione nel processo creativo, Ishiguro ha detto di aver appositamente evitato di riscrivere o correggere le bozze, ma solo di essersi annotato le successive modifiche da apportare una volta concluso l’intero racconto. Mi vien da pensare che Ishiguro, oltre che a costringere se stesso a scrivere, volesse tentare di avvicinarsi il più possibile alla quella dedizione assoluta al proprio lavoro descritta nel romanzo, così da caratterizzare al meglio Stevens tramite le sensazioni che provava durante tutte quelle ore di intensa produzione.&lt;/p&gt;
</content:encoded></item><item><title>A simple Common Lisp web app</title><link>https://scotto.me/blog/2025-04-30-a-simple-common-lisp-web-app/</link><guid isPermaLink="true">https://scotto.me/blog/2025-04-30-a-simple-common-lisp-web-app/</guid><description>A tutorial for the web</description><pubDate>Wed, 30 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the drawbacks I’ve found when dealing with Common Lisp is the lack of documentation available. Too often, I find published libraries without an explanation of how they are meant to be used or only partially documented, and I need to dig into the source code to understand what they do and to see all the functions available. Even though reading source code is a proven technique to improve one&amp;#39;s grasp of a programming language, most other systems come with extensively documented libraries, something appreciated by beginners and a factor that often contributes to a language&amp;#39;s popularity.&lt;/p&gt;
&lt;p&gt;In my opinion, this lack of good documentation is one of the reasons Common Lisp is often seen as challenging for beginners, which makes it harder for the language to become popular.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/lisp_cycles.png&quot; alt=&quot;xkcd.com/224&quot; title=&quot;xkcd.com/224&quot;&gt;&lt;/p&gt;
&lt;p&gt;Some time ago, when I looked for guidance on writing a generic web app, I was surprised by the absence of a quickstart page to help me set up a simple server—something the Python community has provided for &lt;a href=&quot;https://flask.palletsprojects.com/en/stable/quickstart/&quot;&gt;Flask&lt;/a&gt; for many years.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“The most underrated skill to learn as an engineer is how to document. Fuck, someone please teach me how to write good documentation. Seriously, if there&amp;#39;s any recommendations, I&amp;#39;d seriously pay for a course (like probably a lot of money, maybe 1k for a course if it guaranteed that I could write good docs.)”
&lt;a href=&quot;https://www.reddit.com/r/ExperiencedDevs/comments/nmodyl/drunk_post_things_ive_learned_as_a_sr_engineer/&quot;&gt;A drunk dev on reddit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I put together a short tutorial on how to build a simple web app in Common Lisp, inspired by the Clojure tutorial written for &lt;a href=&quot;https://luminusweb.com/docs/guestbook&quot;&gt;Luminus&lt;/a&gt;. The goal is to write a guestbook demo, involving template &lt;em&gt;rendering&lt;/em&gt;, connecting to a database to run queries, and exposing routes to the webpage.&lt;/p&gt;
&lt;p&gt;To follow the tutorial, you need a Common Lisp implementation, like &lt;a href=&quot;https://www.sbcl.org/&quot;&gt;SBCL&lt;/a&gt;, with &lt;a href=&quot;https://www.quicklisp.org/index.html&quot;&gt;Quicklisp&lt;/a&gt;, the dependency manager. I also recommend having a REPL integrated with your IDE, like &lt;a href=&quot;https://github.com/joaotavora/sly&quot;&gt;SLY&lt;/a&gt; for Emacs or &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=rheller.alive&quot;&gt;Alive&lt;/a&gt; for VSCode.&lt;/p&gt;
&lt;p&gt;I’ll use more modern CL libraries that have an interface similar to other languages, so it might be a bit easier to follow along.&lt;/p&gt;
&lt;h2&gt;The server&lt;/h2&gt;
&lt;p&gt;First things first, I created a new Common Lisp project. To do that with a simple boilerplate, I loaded &lt;code&gt;cl-project&lt;/code&gt; in the environment and called the function &lt;code&gt;make-project&lt;/code&gt; with a path and a name for the project.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;&amp;gt; (ql:quickload :cl-project)
To load &amp;quot;cl-project&amp;quot;:
  Load 1 ASDF system:
    cl-project
; Loading &amp;quot;cl-project&amp;quot;
..
(:CL-PROJECT)

&amp;gt; (cl-project:make-project #P&amp;quot;~/guestbook/&amp;quot; :name &amp;quot;guestbook&amp;quot;)
writing ~/guestbook/guestbook.asd
writing ~/guestbook/README.org
writing ~/guestbook/README.markdown
writing ~/guestbook/.gitignore
writing ~/guestbook/src/main.lisp
writing ~/guestbook/tests/main.lisp
T
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I ran this command to let Quicklisp know where my new project is located.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;&amp;gt; (pushnew #P&amp;quot;~/guestbook/&amp;quot; asdf:*central-registry* :test #&amp;#39;equal)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I declared the required libraries in the &lt;code&gt;:depends-on&lt;/code&gt; property so that Quicklisp would download them from the repo and load them into the environment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(defsystem &amp;quot;guestbook&amp;quot;
  :version &amp;quot;0.0.1&amp;quot;
  :license &amp;quot;MIT&amp;quot;
  :depends-on (:alexandria        ;; utils
               :uiop
               :cl-ppcre          ;; regex library
               :cl-syntax-annot   ;; for @export annotation
               :clack             ;; Web libraries
               :lack
               :caveman2          ;; Web framework
               :djula             ;; Template engine
               :cl-dbi)           ;; Database
  :components ((:module &amp;quot;src&amp;quot;
                :components
                ((:file &amp;quot;config&amp;quot;) ;; files into src/
                 (:file &amp;quot;db&amp;quot;)
                 (:file &amp;quot;web&amp;quot;)
                 (:file &amp;quot;core&amp;quot;))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, inside &lt;code&gt;src/core.lisp&lt;/code&gt; (I renamed the file from &lt;code&gt;main&lt;/code&gt; to &lt;code&gt;core&lt;/code&gt;) I added two new functions, to start and stop the server, which will be helpful to use from the REPL.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(defvar *server* nil)

(defparameter *app*
  (lack:builder
	  (:static
      :path (lambda (path)
              (if (ppcre:scan 
                    &amp;quot;^(?:/images/|/css/|/js/|/robot\\.txt$|/favicon\\.ico$)&amp;quot; 
                    path)
                  path
                  nil))
      :root *static-directory*)
  ;; Additional middlewares
    guestbook.web:*web*))

@export
(defun start (&amp;amp;rest args
              &amp;amp;key
                (server :hunchentoot)
                (port 3210)
                (debug nil)
              &amp;amp;allow-other-keys)
  &amp;quot;Starts the server.&amp;quot;
  (when *server*
    (restart-case (error &amp;quot;Server is already running.&amp;quot;)
      (restart-server ()
        :report &amp;quot;Restart the server.&amp;quot;
        (stop))))

  (setf *server* (apply #&amp;#39;clack:clackup *app*
                   :server server
                   :port port
                   :debug debug
                   args))
  (format t &amp;quot;Server started&amp;quot;))

@export
(defun stop ()
  &amp;quot;Stops the server.&amp;quot;
  (when *server*
    (clack:stop *server*)
    (format t &amp;quot;Server stopped&amp;quot;)
    (setf *server* nil)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;*server*&lt;/code&gt; contains the server instance and is defined as a variable since we will need to redefine it. &lt;code&gt;*app*&lt;/code&gt; is the web application wrapped with a layer by &lt;em&gt;Lack&lt;/em&gt;. &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;stop&lt;/code&gt; instantiate the server with some logging, or raise errors if the action is not successful.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Lack&lt;/em&gt; and &lt;em&gt;Clack&lt;/em&gt; are the two libraries I used to wrap the web application. &lt;a href=&quot;https://github.com/fukamachi/lack&quot;&gt;The first one&lt;/a&gt; allows to define a series of middlewares in the server; for example, I used &lt;code&gt;:static&lt;/code&gt; to tell the server where to find all the static assets in the project, inside the directory pointed to by &lt;code&gt;*static-directory*&lt;/code&gt;. Other middlewares are available, like logging, managing sessions or providing authentication features. &lt;a href=&quot;https://github.com/fukamachi/clack&quot;&gt;&lt;em&gt;Clack&lt;/em&gt;&lt;/a&gt; instead is an abstraction layer for the server that provides some parameters to customise it, for example, to quickly swap which server to use between development mode (&lt;code&gt;hunchentoot&lt;/code&gt;) and production (&lt;code&gt;woo&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(unless (null *server*)
    (restart-case (error &amp;quot;Server is already running.&amp;quot;)
      (restart-server ()
        :report &amp;quot;Restart the server.&amp;quot;
        (stop))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this portion of the code, I defined a restart action for the debugger. The Common Lisp debugger always has &lt;code&gt;RETRY&lt;/code&gt; and &lt;code&gt;ABORT&lt;/code&gt; actions for every exception raised. By declaring a &lt;code&gt;restart-case&lt;/code&gt;, we are signalling an error and adding custom choices to the ones offered by default by the debugger. The new option I added is called &lt;code&gt;restart-server&lt;/code&gt; and, if selected, it first &lt;code&gt;(stop)&lt;/code&gt;s the server and then restarts the operation, so the function runs again without raising an error. It’s a smart way to interact with the REPL and improve the developer experience using the language directly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/cl-restart-case-server.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;More on conditions and restart &lt;a href=&quot;https://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://lispcookbook.github.io/cl-cookbook/error_handling.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At the top of &lt;code&gt;src/core.lisp&lt;/code&gt; I set the package definition and some initialisation function calls.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(in-package :cl-user)
(defpackage guestbook.core
  (:use :cl)
  (:import-from :guestbook.config
                :*static-directory*))
(in-package :guestbook.core)

(syntax:use-syntax :annot)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;(syntax:use-syntax :annot)&lt;/code&gt;, from the package &lt;code&gt;cl-syntax-annot&lt;/code&gt;, allows us to use some special decoration notation at the top of the function. At the top of &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;stop&lt;/code&gt; I added an &lt;code&gt;@export&lt;/code&gt; tag, which tells the compiler that a function is exported by the package. &lt;/p&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;I put the configuration parameters for the app into a config file; there are better ways, but they’re not necessary for a project this simple.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;@export
(defparameter *application-root* 
  (asdf:system-source-directory :guestbook))
@export
(defparameter *static-directory* 
  (merge-pathnames #P&amp;quot;static/&amp;quot; *application-root*))
@export
(defparameter *template-directory* 
  (merge-pathnames #P&amp;quot;templates/&amp;quot; *application-root*))

@export
(defvar *config*
  `(:databases 
      ((:maindb :sqlite3 
        :database-name ,(namestring (merge-pathnames &amp;quot;guestbook.sqlite&amp;quot;
                                                     *application-root*))))
    :schema-file &amp;quot;db/schema.sql&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The variable &lt;code&gt;*config*&lt;/code&gt; needs a specific format to be used with &lt;code&gt;cl-dbi&lt;/code&gt;, which is the library that I used to interface with the database. In a real application, it would be good to differentiate between &lt;em&gt;dev&lt;/em&gt; and &lt;em&gt;prod&lt;/em&gt; mode with different configurations used, for example to point to different databases or to use a different server with &lt;em&gt;Clack&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Database&lt;/h2&gt;
&lt;p&gt;I created a new file in &lt;code&gt;db/schema.sql&lt;/code&gt; and put the SQL code to create a message table.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE message (
    id       INTEGER PRIMARY KEY AUTOINCREMENT,
    username VARCHAR(50) NOT NULL,
    ts       DATETIME NOT NULL,
    content  TEXT NOT NULL
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I ran &lt;code&gt;sqlite3 guestbook.sqlite --init db/schema.sql&lt;/code&gt; from the terminal to create and initialise a new database in the project root.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(in-package :cl-user)
(defpackage guestbook.db
  (:use :cl)
  (:import-from :guestbook.config
                :*config*
                :*application-root*))
(in-package :guestbook.db)

(syntax:use-syntax :annot)

(defun connection-settings (&amp;amp;optional (db :maindb))
  (cdr (assoc db (getf *config* :databases))))

@export
(defun db (&amp;amp;optional (db :maindb))
	&amp;quot;Returns a cached database connection for DB (defaults to :maindb). 
  Uses `connection-settings` and `dbi:connect-cached`. 
	
	Usage: (db) or (db :testdb)&amp;quot;
  (apply #&amp;#39;dbi:connect-cached (connection-settings db)))

@export
(defvar *connection* nil)

@export
(defmacro with-connection (conn &amp;amp;body body)
  &amp;quot;Executes BODY with *CONNECTION* dynamically bound to CONN.
CONN should be a database connection object, typically from `db`.

Usage: (with-connection (db :maindb)
         (dbi:do-sql *connection* \&amp;quot;SELECT * FROM users\&amp;quot;))&amp;quot;
  `(let ((*connection* ,conn))
     (unless *connection*
       (error &amp;quot;Database connection cannot be NIL&amp;quot;))
     ,@body))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the first macro of the project. It’s a simple wrapper that provides a new variable &lt;code&gt;*connection*&lt;/code&gt; to use inside the &lt;code&gt;body&lt;/code&gt;. It raises an error if the &lt;code&gt;conn&lt;/code&gt; value that we passed is not initialised. The function &lt;code&gt;db&lt;/code&gt; instead returns a valid connection, cached automatically by the library.&lt;/p&gt;
&lt;p&gt;Then I defined some functions to perform CRUD operations on the database. &lt;code&gt;format-timestamp&lt;/code&gt; converts a universal timestamp value into a readable date-time string.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(defun format-timestamp (universal-time)
  &amp;quot;Converts a universal time value into a human-readable timestamp string,
formatted as &amp;#39;YYYY-MM-DD HH:MM:SS&amp;#39;.&amp;quot;
  (multiple-value-bind (sec min hour day month year)
      (decode-universal-time universal-time)
    (format nil &amp;quot;~4,&amp;#39;0D-~2,&amp;#39;0D-~2,&amp;#39;0D ~2,&amp;#39;0D:~2,&amp;#39;0D:~2,&amp;#39;0D&amp;quot; year month day hour min sec)))

(defun add-message (name message)
  &amp;quot;Inserts a new message into the database with the given NAME and MESSAGE content.&amp;quot;
  (with-connection (db)
    (let ((sql &amp;quot;INSERT INTO message (username, ts, content) VALUES (?, ?, ?)&amp;quot;)
          (ts (get-universal-time)))
      (dbi:do-sql *connection* sql (list name ts message)))))


(defun delete-message (id)
  &amp;quot;Deletes the message with the given ID from the database.&amp;quot;
  (with-connection (db)
    (let ((sql &amp;quot;DELETE FROM message WHERE id = ?&amp;quot;))
      (dbi:do-sql *connection* sql (list id)))))


(defun get-all-messages ()
  &amp;quot;Retrieves all messages from the database, ordered by timestamp descending.
Timestamps are formatted as human-readable strings.&amp;quot;
  (with-connection (db)
    (let* ((sql &amp;quot;SELECT * FROM message ORDER BY ts DESC&amp;quot;)
           (messages (dbi:fetch-all
                      (dbi:execute
                       (dbi:prepare *connection* sql)))))
      (mapcar (lambda (row)
                (setf (getf row :|ts|) (format-timestamp (getf row :|ts|)))
                row)
              messages))))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The Web App&lt;/h2&gt;
&lt;p&gt;Finally we can create the web application which I put inside &lt;code&gt;src/web.lisp&lt;/code&gt;. It’s a bit longer then the other files so I will break it into chunks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(in-package :cl-user)
(defpackage guestbook.web
  (:use :cl
        :caveman2)
  (:import-from :guestbook.config
                :*template-directory*)
  (:import-from :guestbook.db
                :add-message
                :delete-message
                :get-all-messages)
  (:export :*web*))
(in-package :guestbook.web)

(defclass &amp;lt;web&amp;gt; (&amp;lt;app&amp;gt;) ())
(defvar *web* (make-instance &amp;#39;&amp;lt;web&amp;gt;))
(clear-routing-rules *web*)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The web framework I am using is called &lt;em&gt;Caveman&lt;/em&gt;, developed by &lt;a href=&quot;https://github.com/fukamachi&quot;&gt;Eitaro Fukamachi&lt;/a&gt;, who is a very prolific &lt;em&gt;lisper&lt;/em&gt; and the author of multiple libraries that I am using, including &lt;em&gt;Lack&lt;/em&gt;, &lt;em&gt;Clack&lt;/em&gt;, and &lt;em&gt;cl-dbi&lt;/em&gt;, all having great integration with each other. &lt;code&gt;&amp;lt;app&amp;gt;&lt;/code&gt; is a &lt;em&gt;class&lt;/em&gt; defined by the web framework, and I am extending it and instantiating it in &lt;code&gt;*web*&lt;/code&gt;. &lt;code&gt;clear-routing-rules&lt;/code&gt; is a function inherited from &lt;em&gt;Ningle&lt;/em&gt;, another web framework, which clears the route associations inside the web app instance.&lt;/p&gt;
&lt;h3&gt;Templates&lt;/h3&gt;
&lt;p&gt;Our web app needs to render some static templates with data, and to do that there’s a great library called &lt;code&gt;djula&lt;/code&gt;, which allows us to use most of the same tags and filters that &lt;a href=&quot;https://docs.djangoproject.com/en/5.1/ref/templates/builtins/&quot;&gt;Django exposes&lt;/a&gt; in its template engine.&lt;/p&gt;
&lt;p&gt;I’m not going to include the templates source here for space reasons, in the repository they&amp;#39;re in the &lt;code&gt;templates/&lt;/code&gt; folder.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(djula:add-template-directory *template-directory*)

(defun render (template-path &amp;amp;optional env)
  &amp;quot;Renders a Djula template from TEMPLATE-PATH.
ENV is an optional plist of variables passed to the template.&amp;quot;
  (apply #&amp;#39;djula:render-template*
         (djula:compile-template* (princ-to-string template-path))
         nil
         env))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first function call tells &lt;code&gt;djula&lt;/code&gt; where to find the templates in the project. &lt;code&gt;render&lt;/code&gt; is a function that receives a template path as a parameter and compiles the template for better performance.&lt;/p&gt;
&lt;h3&gt;Routes&lt;/h3&gt;
&lt;p&gt;To allow users to perform CRUD operations, we need to expose a few routes to be called by the web frontend. There are two ways to define routes in Caveman, but this one seems a bit clearer to me than using annotations. &lt;code&gt;defroute&lt;/code&gt; is a macro that receives the route path, some parameters like &lt;code&gt;:method&lt;/code&gt; and eventually some arguments, and then defines the body to handle the request. Inside the body, we can access the request object via &lt;code&gt;*request*&lt;/code&gt; and extract data from it using, for example, the function &lt;code&gt;request-body-parameters&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The routes are self-descriptive. &lt;code&gt;/&lt;/code&gt; renders &lt;code&gt;index.html&lt;/code&gt;, passing all the messages from the db; &lt;code&gt;/message&lt;/code&gt; handles POST requests and inserts the message in the database if the parameters conform; and &lt;code&gt;/message/delete&lt;/code&gt; deletes a message. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(defroute &amp;quot;/&amp;quot; ()
  (render #P&amp;quot;index.html&amp;quot;
          (list :messages (get-all-messages))))


(defroute (&amp;quot;/message&amp;quot; :method :POST) ()
  (let* ((body-params (request-body-parameters *request*))
         (name-param (assoc &amp;quot;name&amp;quot; body-params :test #&amp;#39;string=))
         (message-param (assoc &amp;quot;message&amp;quot; body-params :test #&amp;#39;string=)))
    (if (and (consp name-param) (consp message-param))
      (add-message (cdr name-param)
                   (cdr message-param))
      (format t &amp;quot;Missing body parameters: received ~A~%&amp;quot; body-params)))
  (redirect &amp;quot;/&amp;quot;))


(defroute (&amp;quot;/message/delete&amp;quot; :method :POST) ()
  (let* ((body-params (request-body-parameters *request*))
         (id-param (assoc &amp;quot;id&amp;quot; body-params :test #&amp;#39;string=)))
    (if (consp id-param)
        (let ((id (ignore-errors (parse-integer (cdr id-param)))))
          (when id
            (delete-message id)))
        (format t &amp;quot;Missing id parameter.&amp;quot;)))
  (redirect &amp;quot;/&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also added another function, which defines a method on the web app class. &lt;code&gt;on-exception&lt;/code&gt; is a generic function called when an exception occurs in the web application. This method is specific on the parameter type, since it will run only if the exception has code 404 — not found — and in that case I return a specific custom template.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(defmethod on-exception ((app &amp;lt;web&amp;gt;) (code (eql 404)))
  (declare (ignore app code))
  (render #P&amp;quot;404.html&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Run the demo&lt;/h2&gt;
&lt;p&gt;Now I can load the project into the environment with Quicklisp.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;&amp;gt; (ql:quickload :guestbook)
To load &amp;quot;guestbook&amp;quot;:
  Load 1 ASDF system:
    guestbook
; Loading &amp;quot;guestbook&amp;quot;
......................
(:GUESTBOOK)
CL-USER&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I can start the server and point my browser to &lt;code&gt;127.0.0.1:3210&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;&amp;gt; (guestbook.core:start)
Hunchentoot server is started.
Listening on 127.0.0.1:3210.
Server started
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/guestbook.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I can send messages that get saved with a timestamp and then delete them. Pretty simple.&lt;/p&gt;
&lt;h2&gt;Reducing boilerplate&lt;/h2&gt;
&lt;p&gt;Lisp became popular for a series of reasons, and one of the most cited is its ability to reduce boilerplate thanks to the metaprogramming capabilities of the language. Lisp hackers are proud of being able to code solutions faster and do exploratory programming, reducing the size of the code they need to write to reach a solution.&lt;/p&gt;
&lt;p&gt;When comparing the lines-of-code count of my Common Lisp guestbook against the Python-Flask version, the latter seems quicker and simpler to write — 36 lines of Python vs. 229 of Lisp. Lisp’s strength is its ability to model itself according to the problem the developer is solving. In this case, I am dealing with a simple guestbook demo, so it may not be strictly necessary, but to present the language better I will try to reduce the size of the program by hiding some code and configuration.&lt;/p&gt;
&lt;p&gt;First, let’s have a look at the new guestbook app, written in my new custom web framework called &lt;em&gt;flashcl&lt;/em&gt;: 29 LOC, properly formatted and stripped of all comments, and not too hard to read.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(in-package :cl-user)
(defpackage guestbook.core
  (:use :cl :flashcl))
(in-package :guestbook.core)

(init-flashcl :sqlite3 &amp;quot;guestbook.sqlite&amp;quot;)

(defmodel message
  ((username :col-type (:varchar 50) :accessor message-username)
   (content  :col-type  :text        :accessor message-content)))

(defroute &amp;quot;/&amp;quot; ()
  (render #P&amp;quot;index.html&amp;quot; (list :messages (db-all &amp;#39;message))))

(defroute (&amp;quot;/message&amp;quot; :method :POST) ()
  (let ((name (form-param &amp;quot;name&amp;quot;))
        (message (form-param &amp;quot;message&amp;quot;)))
    (if (and name message (&amp;gt; (length name) 0) (&amp;gt; (length message) 0))
      (db-add (make-instance &amp;#39;message :username name :content message))
      (format t &amp;quot;Missing body parameters: received ~A~%&amp;quot; (body-params))))
  (redirect &amp;quot;/&amp;quot;))

(defroute (&amp;quot;/message/delete/:id&amp;quot; :method :POST) (&amp;amp;key id)
  (if id
    (let ((id (ignore-errors (parse-integer id))))
      (when id
        (db-delete (db-find &amp;#39;message id)))
      (format t &amp;quot;Missing id parameter.&amp;quot;)))
  (redirect &amp;quot;/&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where did &lt;code&gt;start&lt;/code&gt; go? It is now part of the &lt;em&gt;flashcl&lt;/em&gt; framework, so it’s imported in the package. Here’s the usage reference.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;;; --- Run the Application ---
;; Call run-app function from your REPL or add it here to run on load.
;; Call stop-app to stop the server.
;;
;; Example: (guestook::run-app) or just (run-app) inside the package.
;; (run-app :port 5000 :server :hunchentoot)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I slightly modified the template delete button to point to the correct route with the &lt;code&gt;id&lt;/code&gt; parameter.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m going to show just some the new code that I added to write &lt;em&gt;flashcl&lt;/em&gt;. I mostly merged the old code in a new file trying to make it as reusable as I could for other web projects, since it&amp;#39;s possible to define new database models and routes quickly, with support for static files and templates. I used &lt;em&gt;mito&lt;/em&gt;, another library from &lt;a href=&quot;https://github.com/fukamachi/mito&quot;&gt;Eitaro Fukamachi&lt;/a&gt;, which is an ORM that works well with SQLite.&lt;/p&gt;
&lt;p&gt;First I imported the library and exported only what’s needed by the user.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(defpackage :flashcl
  (:use #:cl)
  (:import-from #:caveman2
                #:defroute
                #:redirect
                #:*request*
                #:*response*)
  (:import-from #:lack.request
                #:request-parameters)
  (:import-from #:mito
                #:dao-table-class ; Re-export metaclass for use in defmodel
                #:connect-toplevel
                #:ensure-table-exists
                #:select-dao
                #:find-dao
                #:delete-dao
                #:insert-dao)
  (:export ;; Setup
          #:init-flashcl
          ;; App Definition
          #:flashcl-app ;; Variable holding the app instance
          #:defmodel
          ;; Routing &amp;amp; Request/Response
          #:defroute
          #:form-param
          #:render ;; Re-export caveman&amp;#39;s render (or wrap it)
          #:redirect ;; Re-export caveman&amp;#39;s redirect
          ;; Database
          #:db-all
          #:db-add
          #:db-find
          #:db-delete
          #:dao-table-class ;; Re-export metaclass
          ;; Running
          #:run-app
          #:stop-app))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I wrote an initialisation function similar to Flask’s, which sets various variables and creates a new instance of the Caveman webapp.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(defun init-flashcl (db-type db-path &amp;amp;optional (template-dir &amp;quot;templates&amp;quot;)
                                               (static-dir &amp;quot;static&amp;quot;))
  &amp;quot;Initializes Flashcl environment. Sets DB/Template paths, connects DB.&amp;quot;
  (setf *database-path* (get-absolute-path db-path))
  (setf *static-directory* (get-absolute-path static-dir))

  ;; Set Djula&amp;#39;s template directory
  (djula:add-template-directory (get-absolute-path template-dir))
  (format t &amp;quot;Set template directory as ~A~%&amp;quot; (get-absolute-path template-dir))

  ;; Connect to SQLite database
  (handler-case (connect-toplevel db-type :database-name *database-path*)
    (error (c)
      (format *error-output* &amp;quot;~&amp;amp;Error connecting to database ~A: ~A~%&amp;quot; *database-path* c)))

  ;; Define the Caveman2 app instance here
  (setf flashcl-app (make-instance &amp;#39;caveman2:&amp;lt;app&amp;gt;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make it easy to define a new database model for the application, hiding the &lt;em&gt;mito&lt;/em&gt; library, I created a macro that ensures the table gets created correctly. &lt;em&gt;mito&lt;/em&gt; automatically adds a few fields to the table definition, like &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;created_at&lt;/code&gt; or &lt;code&gt;updated_at&lt;/code&gt;, so we don&amp;#39;t have to worry about them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(defmacro defmodel (name slots &amp;amp;rest options)
  &amp;quot;Defines a Mito DAO class and ensures its table exists.
   Example: (flashcl:defmodel comment
              ((name :col-type (:varchar 20))
               (comment :col-type :text)))&amp;quot;
  (let ((class-options options)
        (table-name (string-downcase name))) ; Use singular name by default
    
    ;; Add table name inference if not present
    (unless (find :table-name options :key #&amp;#39;car)
      (push `(:table-name ,table-name) class-options))

    `(progn
      (defclass ,name ()
        ,slots
        (:metaclass mito:dao-table-class)
        ,@class-options)
      ;; Ensure table exists after class definition
      ;; Note: This runs at compile/load time when defmodel is processed.
      ;; Ensure DB is connected before loading code using defmodel.
      (handler-case (ensure-table-exists &amp;#39;,name)
        (error (c)
          (format *error-output* &amp;quot;~&amp;amp;Warning: Could not ensure table for ~A (DB might not be connected yet?): ~A~%&amp;quot; &amp;#39;,name c)))
      ;; Return the class name
      &amp;#39;,name)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I added some database helpers to perform a few CRUD actions on the db.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lisp&quot;&gt;(defun db-all (class-name)
  &amp;quot;Selects all records for the given model class.&amp;quot;
  (select-dao class-name))

(defun db-find (class-name id)
  &amp;quot;Finds a model instance by ID.&amp;quot;
  (find-dao class-name :id id))

(defun db-add (instance)
  &amp;quot;Inserts a model instance into the database.&amp;quot;
  (insert-dao instance))

(defun db-delete (instance)
  &amp;quot;Deletes a model instance from the database.&amp;quot;
  (delete-dao instance))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;To recap, in this tutorial I used some of the latest libraries in Common Lisp to create a simple guestbook webapp. Then I wrote a simple reusable wrapper to make our code more concise, aiming to challenge the Flask framework in Python. The full source code of the two versions it&amp;#39;s &lt;a href=&quot;https://github.com/eliascotto/cl-guestbook&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://github.com/eliascotto/cl-guestbook-v2&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ultimately I would like to give my opinion on using Common Lisp to write a web server. 
Common Lisp shines when dealing with low-level tasks like &lt;a href=&quot;https://blog.funcall.org/lisp%20psychoacoustics/2024/05/01/worlds-loudest-lisp-program/&quot;&gt;small systems programming&lt;/a&gt; or performing &lt;a href=&quot;https://www.grammarly.com/blog/engineering/running-lisp-in-production/&quot;&gt;intensive computation&lt;/a&gt; at scale. When these complex systems need to communicate with the outside world, perhaps via an API, writing a server is the correct way and Common Lisp is capable of delivering that.&lt;/p&gt;
&lt;p&gt;But despite the fact that the language is easily adaptable and comes with performant server libraries (Hunchentoot and Woo), I would say that there are better alternatives for developing generic modern web apps. Hot reloading is now a feature present in many web frameworks, and Common Lisp is not really shining for being ergonomic, nor does it come with many built-ins. Clojure, by contrast, is a modern Lisp dialect with great web frameworks, nice documentation and tons of stable libraries. I would definitely choose the latter for a fresh web project, since with CL I get the feeling that I would end up having to write more code than I should to add custom features and middlewares.&lt;/p&gt;
&lt;p&gt;I would like to give a shout-out to &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=rheller.alive&quot;&gt;Alive&lt;/a&gt;, which is the only Common Lisp extension for VSCode that implements the REPL with features similar to what SLIME and SLY bring to Emacs. The extension is still under development and not yet a full replacement for Emacs, especially during debugging, but it has great features and a lot of potential to introduce Common Lisp to newcomers, thanks to the VSCode web interface.&lt;/p&gt;
&lt;p&gt;Feel free to email me if you have any questions, recommendations, or even complaints.&lt;/p&gt;
</content:encoded></item><item><title>Le parole di Nabokov</title><link>https://scotto.me/blog/2025-03-11-le-parole-di-nabokov/</link><guid isPermaLink="true">https://scotto.me/blog/2025-03-11-le-parole-di-nabokov/</guid><description>Il suo stile, le sue farfalle e i vocaboli di Lolita.</description><pubDate>Tue, 11 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;La mia opinione è che Nabokov non lo si legge per la trama. Ogni suo lavoro è una produzione artistica, uno studio meticoloso della prosa che lascia che i lettori sensibili all’estetica, ammaliati dal ritmo della sua scrittura. Nabokov sfida il lettore spingendolo al limite della sua conoscenza semantica, portando vocaboli poco comuni fuori dal loro contesto, trasformando il testo in un gioco stilistico che affascina ma, che al tempo stesso, scatena un complesso d’inferiorità, quando ci si rende conto che una qualità del genere è quantomeno ineguagliabile.&lt;/p&gt;
&lt;p&gt;Avendo di recente riletto il suo capolavoro &lt;em&gt;Lolita&lt;/em&gt; (ne ho anche scritto &lt;a href=&quot;blog/2024-02-04-lolita-nabokov-ita/&quot;&gt;una recensione&lt;/a&gt;) mi sono appuntato le parole di cui non conoscevo il significato, o di cui non ero del tutto certo, e ho deciso di riportarle in questo articolo con una breve definizione. L’invito a leggere Nabokov è sottinteso.&lt;/p&gt;
&lt;p&gt;Per conoscere e capire l’uomo dietro l’elegante scandalo che fu il romanzo, ho guardato &lt;em&gt;How do you solve a problem like Lolita?&lt;/em&gt; — in italiano, come si risolve un problema come Lolita? —, un documentario prodotto dalla BBC riguardo al libro. Nabokov nacque in una facoltosa famiglia della nobiltà russa, cosa che gli permise di vivere in condizioni privilegiate durante la giovinezza, e grazie alla quale ottenne accesso alla cultura internazionale. Imparò il francese e l’inglese — si dice abbia iniziato a leggere e scrivere in inglese prima che in russo —, e si dedicò a riempire quaderni di poesie romantiche durante l’adolescenza. Con la rivoluzione russa, l’intera famiglia fu costretta a scappare e Nabokov decise di trasferisci a Cambridge per studiare letteratura francese al Trinity College. Successivamente seguì il padre a Berlino, dove divenne famoso tra gli emigrati russi come scrittore e poeta, nonostante alcune difficoltà economiche. Con l’aumentare dell’antisemitismo in Germania precedente allo scoppio della guerra, decise di trasferirsi con la moglie Vera a Parigi, per poi qualche anno più tardi muoversi negli Stati Uniti, a Manhattan, avendo ottenuto un ruolo come entomologo al museo di storia naturale di New York. L’anno dopo divenne professore di letteratura e girò diversi college americani finendo la sua carriera alla Cornell University, dov’è alcune sue brillanti lezioni vennero raccolte  in dei libri, il più famoso dei quali è &lt;em&gt;Lezioni di letteratura&lt;/em&gt;. Le opere che composte negli Stati Uniti, tutte scritte in inglese,  lo resero celebre a livello internazionale. Le più famose; &lt;em&gt;Lolita&lt;/em&gt;, &lt;em&gt;Fuoco pallido&lt;/em&gt; e &lt;em&gt;Pnin&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/nabokov.jpeg&quot; alt=&quot;Nabokov che osserva una collezione di farfalle.&quot; title=&quot;© Marc Riboud / Magnum&quot;&gt;&lt;/p&gt;
&lt;p&gt;Oltre a essere un professore poliglotta, e un autore di talento, Nabokov era un appassionato di farfalle. Studiò e catalogò diverse specie, scrisse articoli per addetti ai lavori, e finì addirittura per curare una collezione di lepidotteri presso il Museo di Zoologia Comparata dell’Università di Harvard. Nel documentario hanno intervistato un responsabile del museo che ha confermato come Nabokov non fosse un appassionato alle prime armi, ma un preciso catalogatore di specie di farfalle. Egli, fece diversi viaggi nel mondo dedicandosi alla ricerca di specie rare, e creò una serie di &lt;a href=&quot;https://www.theguardian.com/books/gallery/2016/may/26/vladimir-nabokov-butterfly-art-illustrations&quot;&gt;illustrazioni delle farfalle che studiava&lt;/a&gt;. Cos’hanno in comune le farfalle e la scrittura? In apparenza nulla. Ma se pensiamo allo studio minuzioso dei particolari che hanno in comune le due attività, capiamo che dedizione e precisione sono requisiti essenziali per entrambe. Che il minuzioso studio dei colori dei lepidotteri possa aver contribuito alla precisa scelta delle parole e del tono nelle sue storie? Non lo sappiamo con certezza. Ciò di cui abbiamo invece testimonianza grazie ad alcune interviste è che gli anni passati da Nabokov come entomologo ad Harvard siano stati tra i migliori della sua vita.&lt;/p&gt;
&lt;p&gt;Tra le due passioni, quella che noi comuni lettori possiamo apprezzare è la scrittura, nell’eleganza della sua prosa, nel ritmo musicale e cadenzato, nella scelta accurata di ogni vocabolo. Il protagonista di Lolita è un professore di letteratura francese in America — coincidenze? — con cattive intenzioni, ma di farfalle alate nel romanzo nemmeno l’ombra.&lt;/p&gt;
&lt;p&gt;Ecco la lista delle parole per me difficili trovate in Lolita.&lt;/p&gt;
&lt;hr&gt;
&lt;div class=&quot;no-indent&quot;&gt;

&lt;p&gt;&lt;strong&gt;Chaperon:&lt;/strong&gt; Donna, per lo più anziana, che un tempo accompagnava una giovane nubile di buona famiglia ai ricevimenti e nei viaggi, per salvaguardarne la rispettabilità.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Marleniforme:&lt;/strong&gt; A forma rotonda e bombata, simile a una mela Marlene, tipica del Sud-Tirolo.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“D’un tratto la mano di Lo scivolò nella mia, e all’insaputa del nostro chaperon io strinsi e accarezzai e avvinghiai quella zampetta ardente per tutto il tragitto. Le pinne del naso marleniforme della guidatrice erano lucide, avendo perduto o consumato la loro razione di cipria…”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bistrate&lt;/strong&gt;: Tinte, oscurate, scurite con il bistro, uba polvere colorante naturale bruno-scura, costituita da idrato di manganese, usata nei colori a olio o ad acquerello.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chador:&lt;/strong&gt; Lungo velo nero, indossato dalle donne islamiche conformemente alla tradizione religiosa. Parola di origine persiana.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Voluttà:&lt;/strong&gt; Il piacere intenso e predominante che si prova nella soddisfazione degli impulsi e dei desideri sessuali&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eclettico:&lt;/strong&gt; Chi, nell’arte o nella scienza, non segue un determinato sistema o indirizzo, ma sceglie e armonizza i principi che ritiene migliori.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reptazione:&lt;/strong&gt; Modo di locomozione caratteristico di molti animali, sia invertebrati che vertebrati (in molti rettili, in particolare i serpenti), per cui il corpo striscia sul suolo e non è sollevato sugli arti, che mancano o sono rudimentali.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gibboso:&lt;/strong&gt; Che porta una gobba.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cicaleccio:&lt;/strong&gt; Il cicalare di più persone insieme; chiacchierio frivolo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solipsizzata:&lt;/strong&gt; Immersa in una visione solipsistica, isolata nella propria soggettività, come se tutto il resto fosse irrilevante o inaccessibile.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Abbarbicato:&lt;/strong&gt; Radicato saldamente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preprandiale:&lt;/strong&gt; Che precede il pranzo.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Io non progettavo di sposare la povera Charlotte per poi eliminarla in un modo volgare, ripugnante e pericoloso, come metterle cinque compresse di bicloruro di mercurio nello sherry preprandiale o qualcosa del genere…”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Farmacopeico:&lt;/strong&gt; Che si riferisce alla farmacopea, ovvero l&amp;#39;insieme ufficiale di norme riguardanti la preparazione, il controllo e l’uso dei farmaci.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nevralgica:&lt;/strong&gt; Pervasa da un malessere acuto, come il dolore lungo il decorso di un nervo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Blusa:&lt;/strong&gt; Camiciotto di tela usato dagli operai e dai pittori durante il lavoro.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Canuto:&lt;/strong&gt; Dai capelli bianchi.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rotocalco:&lt;/strong&gt; Periodico, rivista.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scriminatura:&lt;/strong&gt; La linea che segna la spartizione dei capelli.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Serica:&lt;/strong&gt; Di seta, simile alla seta o legato alla seta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mefitici:&lt;/strong&gt; Che hanno odore fetido, irrespirabile, malsano.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Smargiasseria:&lt;/strong&gt; Chi si vanta di qualità che non ha e di poter fare cose di cui non è capace.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Enfia:&lt;/strong&gt; Rigonfia, tumefatta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Berciava:&lt;/strong&gt; Urlava sguaiatamente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nerboruto:&lt;/strong&gt; Muscoloso, vigoroso, robusto.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Umbratile:&lt;/strong&gt; Ombreggiato, ombroso.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Incartapecorita:&lt;/strong&gt; Che ha assunto, per decrepitezza, aspetto e consistenza paragonabile alla cartapecora; raggrinzito, rugoso.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Muliebre:&lt;/strong&gt; Della donna, relativo alla donna, con riferimento alle sue qualità, alla dignità del suo ruolo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Esecrabili:&lt;/strong&gt; Meritevoli di condanna.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bilioso:&lt;/strong&gt; Pieno di bile, e per estensione irritabile, collerico.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Azzimato:&lt;/strong&gt; Adornato, abbellito con molta cura.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dedalogia&lt;/strong&gt;: Lo studio e alla teoria dei labirinti o delle strutture complesse e intricate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Logomanzia:&lt;/strong&gt; Pratica divinatoria che si basa sull&amp;#39;interpretazione di parole, frasi o suoni per prevedere il futuro o ottenere risposte&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Aveva letto molto. Sapeva il francese. Era versato in dedalogia e logomanzia.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ondinista:&lt;/strong&gt; Chi crede o pratica la culto delle ondine, figure mitologiche legate all&amp;#39;acqua, spesso rappresentate come spiriti o divinità acquatiche simili a ninfe.&lt;/p&gt;
&lt;/div&gt;</content:encoded></item></channel></rss>