Two stories about ChatGPT

Two days ago I tweeted :

I confess. I’ve succumbed and subscribed to ChatGPT Plus.

Eufrasio asked me why, so I replied

It’s great for translating code from one programming language to another. For example, I had a version of Gbloink! running in the browser, I wrote in CoffeeScript 10 yrs ago. There’s no future in CoffeeScript, but just translated it to Python, with GPT doing about 90% of the work

This is true. I’m hoping that I can release a new Python based version of Gbloink! this year. The Python is running in the browser thanks to PyScript But I should also be able to use it for a desktop app. I may even look into Kivy

As an aside if you haven’t seen them, you might be interested to see that I’m actually having a lot of success now with the original (1997 era!) Gbloink! running with modern DAW software :

Anyway, on the main topic of ChatGPT, Abraham asked

How’s it looking so far? Worth every penny?

And I answered :

Currently (since payment) it’s given me some code to add a) auto-complete to the “move card” field in Cardigan Bay. And b) to check broken links in a page.

I haven’t integrated them in yet, but I hope to in the next couple of days. If they work it’s a definite productivity boost.

Which brings us to the second story, as I tweeted today :

Damn! OK. So #chatgpt just bullshitted me (or at least helped reinforce rather than correct my own stupidity)

I wanted to write a script to check broken links out of my wiki. And I got Chat to help me write it in Clojure.

Of course, I was thinking it would be nice to have this running in the browser. A handy component at the bottom of pages that checked all the external links on them to tell me if they were still valid.

So after some back and forth, it seems like me and chat got the code working.

And then I hit the CORS problem …

Of course you can’t run this in the browser. And when asked directly, #chatgpt KNOWS you can’t.

But it’s happily accompanied me along the path of writing a nice interactive Reagent based widget for doing all this work in the browser.

Now I thought I was smart enough to use #chatgpt and NOT be caught out by its bullshit.

And I now realise that’s bollocks.

Yes, I can totally be fooled by it.

I hope I’m wiser now.

Which I think is a good summary of where I am.

Yes, ChatGPT is an incredible productivity boost. And, it’s even good for “rubber ducking”, ie. having “someone” to talk a problem through with. I’m not regretting subscribing. BUT … you ALWAYS have to be alert that it can confidently assert things that aren’t true or don’t make sense.

Current AI is Phenomenal

It really is.

I’ve been completely blown away by the progress that AI has made recently.

Firstly, like most people, I became obsessed with AI Painting programs like DallE and Stable Diffusion. I even decided to become the world’s most unlikely fashion brand, by using AI painting to express a certain fetishism for synthesizers, DJ decks and other toys combined with anachronistic geeky humour.

So you too can wear Gargoyle DJs on a gothic cathedral.

And medieval nuns discussing a Moog.

White Unisex Crewneck T-shirt with Medieval Nuns image 4

And carry a Victorian DJ tote bag

Tote Bag : Victorian DJ image 1

And admire pictures of pieces of fantasy gear like this:

Make Noise Da Vinci Buchla Acrylic Print 30x40 cm / 12x16″ - Horizontal

But even while I was marvelling at the images that AI was producing, I was still sceptical that AI would make much of a dent in programming.

I wasn’t wowed by CoPilot.

But now ChatGPT has, indeed, blown me away. Not sure if it’s just because it has a better UX. Or if it’s really just cleverer than CoPilot.

But it is able to do things like this: I asked it for some file-uploader code in Clojure / ClojureScript (I really need to add an image gallery to Cardigan Bay). Here’s the response:

Sure, here is an example of how you could allow users to upload pictures to a gallery in your app using Ring and ClojureScript:

On the server side, using Ring, you could create a route that accepts file uploads and stores them in a directory on the server. Here is an example:

(defn upload-route
  "Handler for uploading files to the server. Accepts a file parameter
   and saves the file to the specified directory."
  (let [upload-dir "./uploads"
        file (:file request)]
    (if (not (empty? file))
        (spit (str upload-dir file) (:content file))
        {:status 200
         :headers {"Content-Type" "text/plain"}
         :body "File uploaded successfully"})
      {:status 400
       :headers {"Content-Type" "text/plain"}
       :body "Error uploading file, no file specified."})))

On the client side, using ClojureScript, you could create a form that allows the user to select a file to upload, and then submits the file to the server using an AJAX request. Here is an example:

(ns my-app.core
  (:require [ :as xhr]
            [ :as events]
            [goog.dom :as dom]))

(defn upload-file
  "Handler for uploading a file to the server. Accepts a file input element
   and sends the selected file to the specified URL using an AJAX POST request."
  [file-input url]
  (let [file (.-files file-input)
        form-data (js/FormData.)]
    (if (not (empty? file))
        (.append form-data "file" (aget file 0))
        (xhr/send url
                  (fn [event]
                    (let [xhr (.-target event)]
                      (if (= (.-status xhr) 200)
                          (dom/getElement "upload-status")
                          "File uploaded successfully")
                          (dom/getElement "upload-status")
                          "Error uploading file"))))
                  {"Content-Type" "multipart/form-data"}))
        (dom/getElement "upload-status")
        "Error uploading file, no file selected."))))

(let [file-input (dom/getElement "file-input")
      upload-form (dom/getElement "upload-form")]
  (events/listen upload-form
                 (fn [event]
                   (events/preventDefault event)
                   (upload-file file-input "/upload"))))

Note that this is just an example and may not include all the necessary code to fully implement file uploads in your app. You may need to add additional error handling, security checks, and other features to make it work for your specific use case.

OK. So I haven’t tried this yet. Maybe it’s riddled with bugs. But eyeballing it, it looks like a pretty good first draft. I mean, it’s as good as I’d get by googling an example and copying and pasting some code from it. And it took 30 seconds to generate, rather than maybe half an hour concentrated googling. (Which would probably be a couple of hours in terms of finding a window to do it, getting into the zone and concentrating etc.)

So … what do I think now? I’m gonna try this. And if it works … really I’m changing my mind drastically. I thin AI is really going to internalize enough about the structure, syntax, inner coherences of code, that AI based tools are about to completely transform and revolutionise coding.

Which is scary. But also very exciting given how much frustration there still is in software development.

Why I’m not here much

I’m not writing much here on the SDI blog at the moment.

That’s partly because ThoughtStorms is now going so well. Cardigan Bay keeps improving as a tool for helping me knock it back into shape. The wiki / digital garden is still full of cruft and broken links and confusion of course. But also a lot of new stuff. And with Cardigan Bay always running on my local machine, it’s very easy to capture any ideas, notes or interesting links I discover, in that format rather than over here.

That doesn’t mean I’ve given up on this blog though. It just means I need to find new ways to connect the two together.

Also, I’ve been too heavily engaged on Twitter these days. But with the Musk takeover and other issues there, there seems to be real energy for people to get back to the Fediverse and use things like Mastodon. So I’m trying to keep more of my focus and attention there.

My Mastodon ID is

Starting a ClojureScript Project in 2022

I needed to start a simple ClojureScript project in 2022. And it was a faff.

I love Clojure. But creating projects has become much harder because of so many different ways to do it, different and changing tools, and outdated online documentation.

So tl;dr:

I seem to have found a solution that works for me. August 19, 2022

Step 1: Make sure you have up-to-date Clojure CLI tools.

I had Clojure 1.10.1 and none of the tutorials I found worked; and just got incomprehensible error messages.

If you ever see an error message like :

-T is no longer supported, use -A with repl, -M for main, or -X for exec

That’s a definite sign you have an outdated version.

I had 1.10.1 because that’s the current Ubuntu version. It’s outdated, so install CLI yourself. You want 1.11.1 (as of time of writing)

I used

curl -O
chmod +x
sudo ./

Check your version with

clj -Mtools

Step 2: Install clj-new

Useful tool or library from Sean Corfield


Step 3 : Using clj-new install from the figwheel-main template.

clojure -Tclj-new create :template figwheel-main :name myname/myapp :args '["+deps","–reagent"]'

Then run the app with

clojure -M:fig:build

I hope this helps anyone else with similar confusion to me.

For a long discussion / pleading / ranting, see and . And thanks to everyone on Clojureverse who took time and patience to help me get this working.

Why I love Clojure

Here’s some code I’m writing for my day-job.

I have an iterator of IScoreObjects (basically things that live on a musical score or timeline).

I want an iterator of only the Notes. Where Notes are one of the things that implement the IScoreObject interface and can live on the score.

Here’s my code.

import java.util.Iterator;

public class NoteIterator implements Iterator {
     * Takes an iterator of IScoreObjects and returns an Iterator of only the Notes
    Iterator isoIterator;
    Note _next;
    boolean _hasNext;

    public NoteIterator(Iterator isoi) {
        isoIterator = isoi;

    private void findNext() {
        while (isoIterator.hasNext()) {
            IScoreObject n =;
            if (n.isNote()) {
                _next = (Note)n;
                _hasNext = true;
        _next = null;
        _hasNext = false;

    public boolean hasNext() {
        return _hasNext;

    public Note next() {
        Note rv = _next;
        return rv;

On the other hand, here’s how I’d do it in Clojure

(defn just-the-notes [score-objects] (filter .isNote score-objects))

This is literally something like a 20:1 ratio in line count. And the Java needs an extra file of its own. That is insane.

Patterning in Cardigan Bay

Finally done something I’ve been meaning to for a while, and brought Patterning into Cardigan Bay.

That means we now have a :patterning card type which can contain Clojure code (it’s interpreted on the server using Babashka SCI) that renders patterns as SVG embedded in pages.

See this example page on ThoughtStorms which has patterns embedded in it.

Here’s a screenshot of how it looks in Cardigan Bay itself.

Happy 10th Birthday, Smallest Federated Wiki

I second this :

Happy 10th Anniversary to the Smallest Federated Wiki! Congratulations Ward Cunningham! And thanks as well to Mike Caulfield and all those who’ve worked on, iterated, and expanded on its foundations since.

Smallest Federated Wiki is yet another great intuitive idea from Ward Cunningham. Ward is always doing stuff that seems simultaneously low-key and unobtrusive, that you later realize to be both brilliant, and phenomenally useful, after you’ve had long enough to think about it.

This is a delightful combination of qualities.

Cardigan Bay Latest

I’ve been slowly tweaking both Cardigan Bay and ThoughtStorms wiki in the last few months

As you can see, there some slight changes. Most obviously the NavBar is starting to gain some significant functionality.

In addition to typing the name of a page and hitting Go, to go to it, you can also type a ClojureScript expression and have it evaluated by hitting the “Execute” button :

The result appears in a new space called the Transcript which is not a page, but a place for the output of these commands.

The third button on the NavBar is “Search”. Which is a full text search in the wiki.

Obviously, this functionality is also available in cards, to be embedded in a page. There’s a new interactive ClojureScript workspace card which contains code that can be edited and run :

There’s also a search card which can be embedded. You’ll see that in the edit mode there’s a new bar of buttons to embed boilerplate for specific activities. Here we’re going to embed a Search card.

The button adds the boilerplate. We just add our search-term. In this example “SmallTalk”

Save to come out of edit mode, and we have that search embedded as a card in the page.

Other improvements. The bookmarklet receiver now automatically turns certain links into their embeds. Eg. this was bookmarked from YouTube with a single click of the bookmarklet, and is already an YouTube embed card in the InQueue

Finally, not only does Cardigan Bay produce an RSS feed. There’s also now an embed RSS feed card type. This card, in ThoughtStorms, rather recursively, embeds the RSS feed from ThoughtStorms :

Right now, all this stuff is checked into the GitHub / GitLab repositories. It’s not in the next release version yet, but will be, pretty soon.


The Future of Lisp

Another rescued Quora answer to the question : What is the future of the LISP programming language?

I’m writing this answer as an unabashed Clojure fanboi.

The second of these advantages is syntax for data-structures.

(defn f [x y] (the int (+ x y)))

That’s it. You’ve typed the expression (+ x y) as having to return an int. (That’s what (the int sexp) does. )