Creativity and AI Models

Since I’ve retired I’ve taken up reviving a project that I co-wrote over 20 years ago, bringing it into the modern age. The project is Dabo: a framework for writing cross-platform, database-centric apps. Nowadays things like this are done over the web, but in 2004 the state of web applications was pretty limited as far as UI flexibility and database integration. And there were many companies who ran their business on a networked application rather than over the internet, so the market for desktop apps was there.

The project was written using the tools of the day: Python 2.4, wxPython 2.x for the UI classes, along with the database drivers of that era. So bringing Dabo to modern times would take a lot of work. Updating to Python 3.10+ was straightforward enough; I had already worked on several 2->3 projects over the years. Updating the database drivers was also straightforward; most had current versions with the same interface, and the others had been replaced with new products that also shared a similar interface (thanks, Python DB-API!).

The biggest hurdle by far is updating to the current state of wxPython. This is a Python wrapper around the wxWidgets project, which is a C++ cross-platform framework for creating native GUI widgets. wxPython inherits a lot of the C++ feeling and style, and as a result is very un-Pythonic. So when we wrote Dabo, we made our UI layer a wrapper around wxPython. In other words, it’s a wrapper of a wrapper of a C++ library.

You’ve probably never heard of wxPython or wxWidgets – they are relatively obscure these days. And that’s where I think there’s a problem.

I was trying to debug a crash that happened in a part of an app: clicking a button on one tab of a pageframe ran some code, and then switched to a different page. Without fail, switching the page caused a crash with no Python traceback. I asked Claude to debug this for me. It spent a lot of time reading through my codebase, then the entire Dabo framework, and then the entire wxPython project before it could understand where the fault lay. It then confidently pronounced that it understood the issue, and coded the fix. I ran that fix, and it also crashed. Fed that crash report back into Claude, and it went through the same “thinking” steps before pronouncing the new fix. Well, you can guess where this is going. I went through this loop six more times until it finally got a fix that worked. The trouble is, it was one of the ugliest hacks I had ever seen: removing several event bindings, switching the page, and then restoring those bindings.

I’ve seen a lot of reviews of vibe coding where they say to consider Claude and others as “junior developers”, but I would never expect such an ugly hack from anyone, no matter how inexperienced; they would have come to me and said they can’t figure it out, so could I help them with it?

I had an idea as to what caused the crash, as I’ve seen similar crashes before and they almost always involved the event loop. Briefly, events fire all the time, and the framework handles them in the order they are received. So in a case like this, where the code changed the active page, under the hood it fired several events that control updating the UI. These events can sometimes conflict with others that happen around the same time, and cause a weird appearance at best, and a system crash at worst. The trick is to use the “call after” invocation, which instead of firing the events immediately, tells the app to wait until all pending events are processed before handling this one.

I told Claude that its “fix” was unnecessarily hackish and ugly, and had it revert those changes. (Tip: always have tools like Claude work on a development branch just in case it blows up like this). I then changed the one line that set the active page to use the call after design, and the crash went away.

Now I’m not trying to disparage tools like Claude; I’ve used them successfully in the past. What I think is different this time is unfamiliarity: there just isn’t that much code out there that uses wxPython for them to get sufficiently trained on in order to determine the correct approach. You can get great results with apps written in Python, JavaScript, Rust, Go, etc., because there are tons of repos in GitHub in those languages for LLMs to train on. There just isn’t enough training material for wxPython.

Which brings me to the main thought that resulted from this: creativity. How can we ever expect LLMs to come up with something new? They are designed to draw on what’s already been created, and if it hasn’t seen something yet, it is very unlikely to ever come up with that. I’m guessing that there was nothing in the crash report that had a link to a fix using call after, or else Claude would have come up with it as a solution. So in the next few years – the era of “vibe coding”, where LLMs generate the majority of new code – how do we expect new solutions to come about? New versions of the models will be trained on increasingly greater proportions of LLM-generated code; an inbreeding process that can only make newer models more repetitive.

Given this, can we ever expect an LLM to be creative? To propose an approach that has never been done before?

Working With AI

It’s been impossible to not have been bombarded with all the hype about AI ever since ChatGPT. There are now several powerful LLM tools available, and one of the claims is that they can write software better than human programmers. As a software developer myself, and one who’s been doing this kind of thing for longer than many of my colleagues have been alive, you might understand my skepticism.

Before I retired, NVIDIA gave all of its employees access to Perplexity Pro, so that’s the LLM I have the most experience with, but I have tried several others. At first I would ask it to write some Python code to replicate a problem I had already solved, to get a feel for how well it did. Most of the time the solution it provided worked, but just didn’t feel “elegant” – it was much more like a beginner would create. But it did work in most cases.

You may have heard the term “vibe coding” used to describe a non-developer using AI to write code. I have tried that, and the results were spotty at best. I decided to write an app for my iPhone that would be useful when practicing disc golf by measuring the distance of a throw. I had no experience with writing Swift or developing with SwiftUI, so it was about as close to vibe coding as I could imagine.

The process was surprisingly quick: it gave me the basics I would need to set up the app, get the mapping tools working, and implementing buttons. It wasn’t smooth sailing, though, as Perplexity would suggest code that was for different versions of Swift, and they would throw errors. I could then copy those errors and paste them back into the “conversation” with Perplexity, and it would respond with a correction (usually preceded by an apology – nice touch!). I did get something working in a couple of days, so I would say it was a success. Adding additional features, such as voice interaction, proved to be a much tougher endeavor; there were just too many incompatibilities that were hit as it tried to write the more complex interactions.

So based on my meager experimental sample of 1, I would say that vibe coding works, but only for the simplest of cases. It felt like asking a machine to design a house: sure, it could probably come up with something boxy that would work, but once you tried to add some style, it would probably mix things up. It’s a language processor, after all, and doesn’t actually understand anything.

Where I’ve found AI to be helpful is when I need to write some code in a language or environment that I don’t work in often, or solve a strange bug. Let me give you an example from yesterday.

I’ve hosted technical email lists on my servers for over 25 years, and I maintain an archive of every message. It’s all automated and generally works well. Each month it analyzes the previous month’s traffic, and posts a summary.

The June summary for the ProFox list came out on July 1, and it was missing most of the messages for that month. I investigated, and found a problem with the archive software that was pretty straightforward to correct. The problem was that I no longer had the original messages to add back to the archive. Some subscribers did, so I was able to add them back – except for one.

The error was clear enough: the text had a smiley emoji, and the MariaDB database column that holds the text of the message was defined with utf8 encoding. Emojis require multibyte (utf8mb4) encoding, so I ran the ALTER TABLE command to change the encoding. This took almost an hour, as the table has over a half-million records that needed to be re-encoded, but when it completed, I confidently re-ran the command to add the message, but once again it failed – with the exact same error! So I turned to Perplexity, fed it the table structure and the error, and it quickly came back with a solution: not only did the column need to be utf8mb4, the entire table has to be defined with that encoding. So once again I ran ALTER TABLE, but it took over 2 hours this time.

When it finally finished, I re-ran the command, and once again it failed, with the exact same message! I read Perplexity’s answer once again, and noticed that I had missed one part of it: defining the connection in pymysql. I needed to also tell the connection to use the correct charset:

    conn = pymysql.connect(
        host=HOST,
        user=creds["DB_USERNAME"],
        passwd=creds["DB_PWD"],
        db=db,
        cursorclass=cls,
        charset="utf8mb4",     <========
        use_unicode=True,      <========
    )Code language: PHP (php)

I had never added those parameters before, as they were never needed – it worked fine with the defaults. But once I added them, the command to add the message worked flawlessly! So in this case, Perplexity saved the day. I’m sure I would have figured it out eventually, but this was so much faster, as Perplexity runs multiple searches on the web and analyzes the responses in order to come up with its answer to your question.

And that’s what machines will always be better at than humans: doing multiple things at once. I could have run those searches, but going through the results to see which matched what I needed would have taken a whole lot longer than what Perplexity could do. That’s the sweet spot for coding with AI: not having it design your application or its interface, but solving those edge case bugs that would take you a much longer time to figure out.

Day 53: An Engineering Mindset

I have spoken out many times about how much I dislike software developers granting themselves titles that include the terms “engineer” or “architect” in them. Those are titles that are earned through years of hard study and certification, just like doctors and lawyers.

Software development is not engineering. If I want to build a vaulted ceiling in my home, there are tables of the properties of materials that give the acceptable spans for any given size of component. If I have a 20-foot span, I can look at those tables and determine what I would need with steel beams, engineered wood beams, or plain 2×10 lumber.

Engineered Lumber & Beams | NICHOLS LUMBER AND HARDWARE
Each of these engineered wood beams has a known maximum span.

There’s no need to debug this; these are known values, along with gravity, weight, and all the other things that go into building design. And you could use a table for materials from 100 years ago, and it would still work today. Knowing how to apply these values in the structure behind the design of buildings takes years of study before one can legally call themselves an architect or an engineer.

The most apt term I’ve heard for a software developer is “gardener”. It’s accurate, as gardens take up-front planning, and like software, they need to be maintained. Both also have their share of bugs!

So while I won’t call myself an engineer, I do have an engineering mindset. What I mean by that is I strive to be methodical about things like program structure, testing, code review, version control, and the like. I enjoy working with teams who are like-minded in that regard.

An engineering mindset isn’t limited to software development, though. I find I do that with every task I do. Take washing dishes, for example. We don’t have a dishwasher, so they all need to be washed by hand. Nor do we have a drainboard, so they have to go on a towel by the sink. I don’t just wash them in the order that they are piled in the sink; instead, I re-arrange them so that they are stacked in the order to be washed in the left sink basin, with the right sink basin empty. Why? Because the drying area is on the right, and this makes for a neat left-to-right flow. First the flat dishes get washed and placed in the right sink. They are then rinsed off, then placed vertically leaning against the wall to help them dry evenly and quickly. Then the bowls and other things with depth are washed, with the eventual placement in the drying area determining the order. Finally the small things like utensils are washed and placed in any available spot left on the towel. This allows the most efficient use of the limited drying area while still allowing things to dry fully.

The other thing I try to optimize for is to minimize the use of hot water. I’ve come up with some routines for group wetting and rinsing that has cut the amount of hot water usage. I’ve even played around with how an item is handled while being rinsed in order to get the most efficient rinsing of all surfaces.

I have been developing this process over the 4 years since we moved into this small house, and I believe I have it down to as efficient a flow as possible. I’ve mentioned this to people, and their reaction is along the lines of “wow, you’re pretty OCD!”. But this isn’t anything like OCD: people who suffer from that condition say that they are perfectly aware that their actions are silly or even harmful, but are unable to stop doing them. I choose every one of these steps because it makes sense. If someone were to show me an even better way to do it, I’d switch in an instant.

That’s an engineering mindset: taking a process, even one as mundane as washing dishes, and always thinking of ways to optimize it. It doesn’t feel like work, though, it’s more like solving a puzzle. Some people like crossword puzzles or jigsaw puzzles; I like solving dish washing puzzles!

Day 52: Happy 10th Birthday, OpenStack!

I just saw this announcement from the OpenStack Foundation about OpenStack’s 10th birthday! Yes, 10 years ago this week was the first OpenStack Summit, in Austin, TX, with the public announcement the following week at O’Rielly OSCON. Yet most people don’t know that I played a very critical role in the beginning!

OpenStack began as a joint venture between Rackspace (my employer at the time) and NASA. I was on the team at Rackspace that developed and supported its aging cloud compute services, and we were looking to develop something from scratch that could be much more scalable than our current system. Around that time Thierry Carrez saw an announcement from a group at NASA about their development of a compute virtualization system, and suggested to the powers that be at Rackspace that this might be a better way to go instead of developing the whole thing ourselves. From that followed a lot of discussion among the executives at Rackspace, as well as some conversations with NASA, and the conclusion was that we would team up. One of the first things to do was to get the developers for both groups together to discuss things from a more technical perspective. And this is where I believe that I made a critical decision that, had I chosen wrong, might have resulted in OpenStack never happening.

The NASA team was a consulting group, Anso Labs, and they were arriving in San Antonio, and we had plans to take them out to dinner, but no idea where. It was then that I suggested The Cove, a local place with lots of outdoor seating, a relaxed atmosphere, good beer, and delicious food. We had a great time that evening, and we all got to know each other. Had we been in a more conventional restaurant, people may have only gotten to know the people sitting next to them, but since The Cove is open seating, we moved around a lot, talking about both the technical stuff and personal stuff.

Over the next few days we began reviewing the code and exchanging idea on what needed to be developed next, and those discussions went very smoothly, getting a lot done in a short period of time. I still maintain to this day that without that first night having beer and food at The Cove, OpenStack might never have become the success that it did.

You’re welcome.

Day 47: Python Virtualenvs

If you’re not a Python programmer, you probably won’t find much in this post. Sorry.

If you are a Python programmer, then you probably know about virtualenvs, or virtual environments. They allow you to create several different Python environments to work in: each can have it’s own version of Python, as well as its own installed packages. This means I can work on a project that has particular requirements, and then switch to a project with completely different requirements, and the two won’t affect each other.

A while ago I used to use vitualenvwrapper, which made working with virtualenvs a lot easier. But as I switched to Python 3 over the past few years, I started to have some issues where it didn’t work correctly (sorry, I no longer recall precisely what those issues were). I’m sure a lot had to do with the addition of the venv module into Python 3, which allowed you to create a virtualenv by running python -m venv /path/to/virtualenv.

For a while I ran all the commands manually, but I have the quality that makes for good programmers: I’m lazy. A lazy person doesn’t want to do any more work than they have to, so if I can automate something to save time over the long run, I will. And here’s what I came up with:

export VENV_HOME=$HOME/venvs

function workon { 
    if [ -z $1 ]
    then
        ls -1 $VENV_HOME
    else
        source $VENV_HOME/$1/bin/activate
    fi
}

function mkvenv {
    python3 -m venv $VENV_HOME/$1
    workon $1
    pip install -U pip setuptools wheel
    pip install ipython pytest-pudb requests
}

function rmvenv {
    command -v deactivate
    rm -rf $VENV_HOME/$1
}

_venvdirs()
{
    local cur="$2"
    COMPREPLY=( $(cd $HOME/venvs && compgen -d -- "${cur}" ) );
}
complete -F _venvdirs workon rmvenv 
Code language: Bash (bash)

These lines should be added to your .bashrc in Linux, or your .bash_profile for Macs. I haven’t tried them with zsh yet, so no guarantees there. Let’s go over what these lines do.

Line #1 defines the directory where the virtualenvs will be stored. You can store them anywhere; it doesn’t make any difference.

Lines #3–10 define the workon function, which activates the specified virtualenv, or lists all virtualenvs if none is specified. Lines 12–17 define the mkvenv function, which creates a new venv, and lines 19–22 define rmvenv, which deletes virtualenvs when you no longer need them.

I’d like to point out 2 lines in mkvenv that you can customize. Line #15 updates the installed versions of pip, setuptools, and wheel. If for some reason you don’t want the latest versions of these, edit or remove that line.

Line #16 is more interesting: nearly every virtualenv I create needs these packages installed. Rather than install them one-by-one, I add them when I create the virtualenv. If you have different packages you always want available, edit this line.

Finally, lines #24–29 are of utmost importance to someone lazy like myself: they provide auto-completion for the other commands. I had to learn about bash completion to get that working, but it turned out to be much easier than I had imagined.

Here is a gif showing it in action:

Try it out! Let me know if you find this useful, or if you have suggestions for improvement.