Monday, March 31, 2025

Avoiding Stringly Typed Code

It can be tempting to implement certain objects by their printed representation. This is especially true when you call out to other programs and pass the parameters in command line arguments and get a result back through the stdout stream. If an object is implemented by its printed representation, then serialization and deserialization of the object across program boundaries is trivial.

Objects implemented by their printed representation are jokingly referred to as “stringly typed”. The type information is lost so it is possible to pass strings representing objects of the wrong type and get nonsense answers. There are no useful predicates on arbitrary strings, so you cannot do type checking or type dispatch. This becomes a big problem for objects created from other utilities. When you call out to a bash script, you usually get the response as stream or string.

The solution? Slap a type on it right away. For any kind of string we get back from another program, we at least define a CLOS class with a single slot that holds a string. I define two Lisp bindings for any program implemented by a shell script. The one with a % prefix is the program that takes and returns strings. Without the % it takes and returns Lisp objects that are marshaled to and from strings before the % version is called. The % version obviously cannot do type checking, but the non-% entry point can and does enforce the runtime type.

Sunday, March 30, 2025

Keep a REPL Open

I keep a REPL open at all times whether or not I’m actually doing Lisp development. It’s my assistant that evaluates random expressions for me. I’ll script up little tasks in Common Lisp rather than in Bash. When I need to rapidly prototype something larger, I’ll switch to the REPL and do it there.

At work, my REPL has accumulated a bunch of code for talking to systems like GitHub, CircleCI, and LDAP as well as our in-house tools. These are utilities for my use only. I don’t write mission critical apps in Common Lis. No one else in the company uses it, and it is more important that the code be maintainable by the rest of the team than that it be written in a language I like. So I write the mission critical code in Python, or Golang, or Java, or whatever the rest of the team is using. I keep my Common Lisp to myself. I have, however, used it to protype code that evetually ends up ported to Python or Golang.

On occasion, I’ve wanted to quickly share some functionality before I have taken the time to port it. I’ve found two ways to do this. The first is to slap a web server on it. I use Hunchentoot for this. I translate JSON to Lisp coming in to the web server and Lisp back to JSON going out. This is all you effectively need for a black-box microservice. There have been a couple of transient projects where the whole thing was not expected to be maintained for a long time and by anyone other than me, so I can just throw up a microservice and tell my colleagues to hit it with a curl command.

The second way is to create a docker image that contains the Common Lisp code and all of its dependencies. It can take a bit of work to configure a lisp setup in your environment, so having it hiding inside a docker image allows me to correctly set up the Lisp environment along with the Lisp interpreter and the rest of the code. My colleagues can just pull and run the container and it will work. Again, this is only for small, throwaway projects that no one else is expected to modify or maintain. For anything that is mission critical or is expected to be shared at some point, I write it in Python or Golang or Java, etc.

I could have written these as a series of Bash scripts or Python programs, but when you start connecting a series of these together, you quickly run into the limitations of using a pipe to talk between programs. My Lisp scripts all reside in the same address space, so they can share structured data without any fancy marshaling protocol.

Saturday, March 29, 2025

Angry Fruit Salad

I like to program in living color. My color scheme is definitely “angry fruit salad”, but there is a method to my madness.

My eyeglasses have a very strong prescription. Chromatic aberration is significant at the edges of my field of vision, so it is important that text be mostly monochromatic, or it will split into tiny glyph-shaped spectra. So my main text color is green on a black background, like a terminal from the 1970s. From there, I chose cyan for comments in the code because it is easy to read. I generally favor the warmer colors for the more “active” elements and the cooler colors for the more “passive” ones, but there are many exceptions.

I have found that my brain gets used to the colors. When something shows up in an unexpected color, it immediately looks wrong, even if I don’t know why. I can leverge this effect by using a very wide variety of colors for different semantic elements. I’m not consciously aware of the semantic meaning, I can just tell if the code looks the wrong color.

So my code looks like the Vegas strip: gaudy, neon colors fighting for attention. I’m sure it would drive many people up the wall. A VSCode theme sort of based on this is available at https://github.com/jrm-code-project/VSCode-Theme.

Friday, March 28, 2025

Vibed into non-functioning

Continue vibing? Well, why not go all the way.

The AI wasn’t doing so well with the main game loop, so I gave it enough help that a blank window would come up. The window would respond to the X key being pressed in order to exit, but you could also close the window to exit as well.

I told the AI that I wanted a grid of tiles. Some tiles had mines. The remaining tiles had an integer which was the number of mines in adjacent squares. The AI wanted to load some textures from files 0.png through 8.png. I asked it to generate those files, but it didn’t want to. So I broke out Paint and generated some crude 32x32 png images of numbers, a mine, a blank, and a flag.

The AI tried to load these images directly, so I had to instruct it that you need a dependency on SDL2-image and that you can load the image on to a surface, and then you can load a texture from the surface (think of a texture as a bitmap on the GPU and a surface as a bitmap on the CPU). There were several rounds of trying the code, getting an error, and pasting the error in to the AI. As per the philosophy of vibe coding, I just accepted the suggested changes without looking at them. I did have to direct it to not to try to “use” packages because that simply introduced name conflicts.

I got to the point where I could compile and load the game so far with no errors. I was testing the code at each step. It wasn’t making much progress in so far as displaying anything, but it at least didn’t regress.

Until it did. I had vibed to the point where I got a nice black rectangle on the screen that did not display anything or respond to any input. No errors were printed. Time to debug. The problem is that I only had a vague idea of what it was doing. I wasn’t paying much attention to changes being made. I dove into the code that had been generated.

What a mess. I had my suspicions as to what was wrong. Some of the newly added code needed to use the SDL2 image library. It needs to initialize the SDL2 image library, load the surfaces, and load the textures in that order. When it exits, it has to unload things in reverse order. When I wrote my Platformer Tutorial, I wrote a set of with-... macros that would pair up loading/unloading and initialize/uninitialize steps with an unwind-protect. If you use the with-... macros, you automatically get the LIFO order of operation that you need for the code to function, and the unwind-protects make sure that the uninitialization occurs even if you error out or abort the application.

The vibed code had none of this. It didn’t know about unwind-protect. It didn’t even know about nesting. It simply tried to run the initialization code first, the inner code next, and the cleanup code after that. But it combined the code through concatenation, not composition, so the necessary LIFO properties were absent. In addition, the initialization code was not paired with the cleanup code. It was pure coincidence that a cleanup happened after an initialization. The initialization code was spread about several functions in an ad hoc manner and the cleanup code was clumped in different sections. It was spaghetti code, and you needed to analyze it carefully to determine if the code initialized things in the right order or cleaned up correctly. One obvious bug was the code destroying the surfaces while the textures were still in use.

I poked at it a little bit, but there was no easy way to massage the code into a working state. It was just too disjoint. I eventually just deleted the vibed code. Firt of all, it didn’t work. Second of all, when I removed it, I regained the lost functionality of the close box and the X key for exit. It is a bad sign when removing code increases functionality.

Vibing is 0 for 2 at this point. If I have time in the next few days, I may revisit this once again and see how much hand-holding I have to give the AI to generate a working display.

Thursday, March 27, 2025

AI Silliness

In The Cyberiad, Stanislaw Lem tells the story of two robots, Trurl and Klapaucius, who are master constructors and are quibbling friends. One day, Trurl builds an artifical intelligence. He shows it off to Klapaucius, who is unimpressed. Klapaucius gives it increasingly absurd tasks, irritating Trurl. One of the challenges is to write a poem, lofty and tragic, and full of pathos, six lines long. About a haircut. And every word beginning with the letter S. Trurl objects, but the AI is up to the task

Samson snored.
She scissored short.
Sorely shorn, Soon shackled slave, Samson sighed,
Silently scheming,
Sightlessly seeking
Some savage, spectacular suicide.

It was science fiction 60 years ago, but our current LLMs can handle this easily. I think you can guess the author and prompt I put in for this:

The Unspeakable Syntax: A Tale of Lispian Horror

It was on a night fraught with cyclopean terrors that I first delved into the accursed tomes of programming lore known to the initiated as the Common Lisp Hyperspec. I had been warned by my colleagues — wizened wretches whose trembling fingers bore the mark of years lost to the parentheses — that this was no ordinary language. But my hubris, my insatiable curiosity, drove me to confront the dark corners of syntax mankind was not meant to know.

Upon the screen unfolded an incomprehensible vista of nested parentheses — lines of code whose curvatures undulated like the tentacles of some primordial beast. They defied reason and mocked the laws of Euclidean space. The parentheses seemed to breathe, to writhe, to multiply in grotesque fractal patterns that scraped against the outer edges of my sanity.

Each ‘car’ and ‘cdr’ was not merely a construct of the language, but an invocation — arcane words that seemed to echo from the abyssal void, summoning constructs whose purposes were shrouded in madness. The recursive forms spiraled infinitely inward, a pitiless vortex from which there could be no escape. My attempts to comprehend the structures were as futile as a mortal gazing upon the boundless chaos of Azathoth, the blind idiot god at the center of all things.

The culmination of my horror came as I confronted a macro. It was a labyrinthine incantation, a concatenation of symbols that seemed to weep with ancient and forbidden knowledge. It whispered to me secrets of the universe I dared not fathom, and though I averted my gaze, its presence imprinted itself upon the fragile canvas of my mind. I knew then that this language was not designed by mere mortals. No, it was the work of entities beyond comprehension—beings for whom the manipulation of symbols is as natural as the shifting of tectonic plates.

The syntax of Common Lisp was not a tool for mankind, but a trap — an eldritch puzzle whose solution would unravel the fabric of reality itself. And now, as I type these words, I can feel the parentheses closing in, their curved forms encircling me, threatening to enclose me in an eternal recursion from which there can be no escape. May the gods have mercy on my soul, for I have gazed into the syntax, and the syntax has gazed back into me.

Wednesday, March 26, 2025

Hard Won Esoteric Knowledge

I do my development in Ubuntu-22.04 Linux running on the Windows Subshell for Linux. I recently got a laptop refresh and the latest software doesn’t run. The fix is obscure, so I thought I’d document it.

sbcl runs fine out of the box in WSL2, but I’m encountering a bug where TCP connections to one particular server are being left in the CLOSE_WAIT state indefinitely. After several minutes, I hit the limit on the number of open files.

The “right thing” would be to track down who isn’t closing the connection properly, but it’s only a few hundred connections. It appears that ulimit is set to 1024, which is pretty easy to hit with this bug. Bumping ulimit to something more reasonable is a lazy workaround. It isn’t a solution — I’m still leaking open files — but I’ll be able to leak thousands of them without having problems.

But increasing nofiles turned out to be a problem. I edited all the magic files in /etc until they all said I could have 131071 open files. When I re-started WSL, all the ways I could start a shell agreed that the ulimit was 131071, yet when I started sbcl and ran this:

(uiop:run-program "prlimit" :output *standard-output*)

RESOURCE   DESCRIPTION                             SOFT      HARD UNITS
AS         address space limit                unlimited unlimited bytes
CORE       max core file size                         0 unlimited bytes
CPU        CPU time                           unlimited unlimited seconds
DATA       max data size                      unlimited unlimited bytes
FSIZE      max file size                      unlimited unlimited bytes
LOCKS      max number of file locks held      unlimited unlimited locks
MEMLOCK    max locked-in-memory address space  67108864  67108864 bytes
MSGQUEUE   max bytes in POSIX mqueues            819200    819200 bytes
NICE       max nice prio allowed to raise             0         0 
NOFILE     max number of open files                1024   1048576 files
NPROC      max number of processes                62828     62828 processes
RSS        max resident set size              unlimited unlimited bytes
RTPRIO     max real-time priority                     0         0 
RTTIME     timeout for real-time tasks        unlimited unlimited microsecs
SIGPENDING max number of pending signals          62828     62828 signals
STACK      max stack size                       8388608 unlimited bytes
NIL
NIL
0 (0 bits, #x0, #o0, #b0)

The limit was at the old value of 1024.

WSL launched sbcl without a shell, so the ulimit setting was not being run.

The solution is easy, but it took me a long time to figure it out. Not only do you need to edit all the magic in /etc, and add ulimit statements to your .bashrc, you should also add ulimit statements to your .profile, and then instruct wsl to launch your program under a login shell:

(require ’sly)
(setq sly-lisp-implementations
      ’((sbcl  ("C:\\Program Files\\WSL\\wsl.exe"
                      "--distribution-id" "{df4f07a6-2142-405c-8a6a-63f1ca3a7e8d}"
                      "--cd" "~"
                      "--shell-type" "login"
                      "/usr/local/bin/sbcl")
               )))

This bit of insanity allows me to run sbcl with 131071 open files in Linux as my inferior lisp program in a Windows Emacs running SLY. (Running Emacs under Windows gives me a way to use a modified Dvorak keyboard. I could run Emacs in the Linux subsystem, but the Wayland server is in a container and doesn’t let you modify the keyboard.)

Tuesday, March 25, 2025

Vibe Coding in Common Lisp, continued

I unwedged the AI with regard to the package system, so I asked the AI to write the game loop.

Now there are a number of ways to approach a game loop, but there is one strategy that really wins: a nested set of with-... macros. These win because they tie resource management to the dynamic execution scope. You write with-window, and upon entry to the form, a window is allocated and initialized and comes into scope during the body of the code. When the body returns, the window is destroyed and deallocated.

These with-... macros are built around allocation/deallocation pairs of primitives that are called from an unwind protect. The abstraction is the inverse of a function call: instead using a function to hide a body of code, you use a function to wrap a body of code. The body of code doesn’t hide in the callee, but is passed as an argument from the caller. One important feature of programming in this way is that resources are never returned from a function, but are only passed downwards from the allocation point. This keeps objects from escaping their dynamic scope.

The entry to the game loop consists of a few nested with-... macros that initialize the library, allocate a window, allocate a drawing context, and enter a event loop. When the event loop exits, the resources are torn down in reverse order of allocation leaving the system in a clean state.

But the AI did not use with-... macros. The code it generated had subroutines that would create a window or allocate a drawing context, but it would assign the created objects into a global variable. This means that the object is set to escape its dynamic scope when it is created. There is nothing to prevent (or even discourage) access to the object after it has been deallocated. There were no unwind-protects anywhere, so objects, once allocated, were eternal — you could never close a window.

In the large, the code was built to fail. In the small, it immediately failed. Calling conventions were not even followed. Keyword agument functions were called with positional arguments, or with an odd number of arguments, irrelevant extra arguments were passed in, the AI would pass in flags that didn’t exist. We’ll grant that the AI does not ultimately understand what it is doing, but it should at least make the argument lists superficially line up. That doesn’t require AI, a simple pattern match can detect this.

The event loop did not load, let alone compile. It referred to symbols that did not exist. We’ll we can expect this, but it needs to be corrected. When I pointed out that the symbol didn’t exist, the AI began to thrash. It would try the same symbol, but with asterisks around it. It would try a variant of the same symbol. Then it would go back and try the original symbol again, try the asterisks again, try the same variant name again, etc. There is nothing to be done here but manual intervention.

There are some macros that set up an event loop, poll for an event, disptach to some code for that event while extracting the event particulars. You can roll your own event loop, or you can just use one of pre-built macros. When the AI began to thrash on the event loop, I intervened, deleted the code it was thrashing on and put in the event loop macro. The AI immediately put back in the code I had removed and started thrashing on it again.

Again, it is clear that the AI has no knowledge at all of what it is doing. It doesn’t understand syntax or the simplest of semantics. It cannot even tell if a symbol is bound to a value. Even the most junior developer won’t just make up functions that are not in the library. The AI doesn’t consult the documentation to validate if the generated code even remotely passes a sniff test.

You cannot “vibe code” Common Lisp. The AI begins to thrash and you simply must step in to get it unwedged. It doesn’t converge to any solution whatsoever. I suspect that this is because there is simply not enough training data. Common Lisp would appear to need some semantic understanding in order to write plausibly working code. Just mimicking some syntax you found on the web (which is ultimately what the AI is doing) will not get you very far at all.