- Neh Thalggu
- Posts
- The Good, the Bad and the Sloppy
The Good, the Bad and the Sloppy
Here's a unified theory of Generative AI, that combines what I'm working on, what's great about using AI as a coding assistant, and all the other societal issues with generative AI.
It can be summed up in a single sentence. "If a task can be done by generative AI, it didn't need doing in the first place"
You see this pattern repeatedly. People who use AI to expand a brief message into an email. And people using AI to summarise emails back into key points. At a meetup I met a venture capitalist who was excited that AI could summarise "pitch decks" for her. I later came across people using it to generate pitch decks. It's the same for documents. Same for academic research papers. Same for sales funnels vs. price comparison sites. Same for plot summaries and AI generated videos. It's all either adding redundant padding or it's compression to remove that padding again.
AI can expand your initial idea into a larger more impressive sounding or looking thing. But the only thing YOU really have to say is whatever original prompt you gave it. So why couldn't you just pass that nugget of original idea or information directly to the audience?
The fact that we feel we can't do this is mainly because of "handicap signalling" : our tendency to judge any idea communicated to us by the extra work that's gone into presenting it. That's true when we judge a company by the "professionalism" of its web-site, a talk by its slides, a YouTube video by its lighting, a student's understanding by the coherence of their essay, or the importance of a research paper by the systematic way it handles its references and citations.
In all these cases we are judging the content by the form. Using our assumptions about how hard it is to achieve the form, as a proxy for the seriousness and effort that went into producing the content. And therefore its likely value.
And to the extent that the form WAS genuinely expensive to produce, this was a reasonable assumption. Failing companies can't afford sophisticated brand-building. Stupid students can't organize their thoughts into an essay. The signal is meaningful because it is expensive.
But now generative AI brings a crisis, because it breaks that heuristic. Suddenly generative AI can make an impressive looking website or impressive sounding research paper without this offering any insight into the quality of the content.
It's this cheap impressiveness of form, stripped of any connection to the underlying value of the content, that we call "slop". And we're terrified of a tsunami of the stuff overwhelming us. And overwhelming our critical capacities to make any evaluations at all. Even within the visual arts, we don't hate the specific look of an AI produced picture as much as we hate that there's no way to retrieve any underlying artistic impulse and commitment from that final image. So how can we know whether we are to appreciate it?
Now, in an ideal world, we'd recognise all this and we''d take the rise of generative AI and the breakdown of handicap signalling, as an opportunity to just tear down the whole stupid bullshit edifice.
We'd normalise sending one or two line messages to each other at work rather than more formalised emails. We'd normalise replacing corporate websites with APIs to plain tables listing products, services and prices so that our bots could find the best deals for us. We'd normalise organising academic research into databases of annotated research results. While "papers" would be deprecated in favour of brief announcements and explainers.
In the absurd but resonant phrase of Lu Wilson, we'd "normalise sharing scrappy fiddles"
And we'd educate for this. We'd educate children and graduate students to focus on identifying the valuable message nugget, and how to judge that. We'd teach them to ignore phatic fluff and judging books by their cover. We'd teach them to communicate their ideas successfully in 400 word summaries, not long dissertations. We'd teach them to work with databases to manage the larger content of their research. And the best practices of managing data or following links to other resources.
(Aside: Isn't it amazing that in 2025, most people read on phones, and academia hasn't even managed to ditch PDFs - the least phone-friendly format in existence - as it's standard way of disseminating information. That's how strong the lock-in of traditional formats is.)
But we probably aren't going to do any of this, because few people want to give up the illusion that form will help us judge content. And so we'll enter an arms race of increasingly overproduced work. A kind of potlatch of gratuitous energy and resource consumption, as we push up our AI usage and continue trying to outdo each other's impressiveness in a zero-sum game for attention and "professional credibility". We'll burn AI to cheat at homework. And burn AI to catch the cheats.
In this sense, the tragedy of generative AI is to become like Bitcoin. A system of catastrophic and unnecessary wastage. But unlike Bitcoin, this is neither inevitable nor inherent in the technology. It's not even a catastrophe created BY the technology. It's a catastrophe created by human culture, our deference to impressive signals, and our inability to adapt to the technology.
We absolutely could, in principle, adapt to use AI positively and sparingly, by stripping out a lot of our handicap signalling, if only we had the collective wisdom to do so.
The Future of Coding
So let's get back to making software. Programmers are in a slightly different position to everyone else. The unnecessary verbosity and complexities of our field are NOT because we are trying to impress the computer with competitive signals. They are literally just accidents that have crept into our technology stacks over the years. Modern programming still needs so much unnecessary boilerplate. And by boilerplate I mean any chunk of low information code or activity we haven't managed to compress yet. It's low information because it's so similar to the activities of all the other programmers over the years. In code. In configuration. And in sysops practice etc.
But this is something that a well trained AI, that has seen a million similar software projects, can help with. Buried in its weights are these common patterns. And we only have to hint at them in our prompting and the AI can pull them them out and apply them for us. Using an AI coding assistant is genuinely wonderful when it saves us a tonne of work writing all this excruciating boilerplate ourselves.
But this is not the endgame. Beyond having AI write code for us, we as programmers are in the fortunate position that we understand this principle of AI as compression. We have a better option. We can identify other redundancies and commonalities in our work, and incorporate these too into new, more concise and expressive notations. This is, after all, what we've been doing to improve our field since the first programming languages moved beyond basic machine instructions. The arrival of AI shouldn't be the end of this process. But a spur to yet more of it.
And in doing so we will actually reduce the amount of required boilerplate in our field. And therefore the amount of AI assistance. When there's less boilerplate, there's less need for AIs to produce it. And less need for so much generative AI at all.
The future is, or should be, not locking in the inefficient languages we have at the moment, just because the AI is so good generating bucket loads of JS and Python source code. The future should be working together with the AIs to figure out the next redundancies, and squeezing them out of the system. Pushing them into the plumbing of the language itself, where purely mechanistic computation will successfully expand our nuggets of requirement into executable code. Coming up with compact, low-redundancy notations like this is our equivalent of normalise sharing scrappy fiddles.
Of course, I've been paying attention to, and thinking about, the future of coding area for a while now. But for a couple of years I was concerned that AI assistance might be the end of our projects to push the art of programming language design forward. No need for new programming languages if there are no new programmers, right?
But now I realise that's the wrong way of seeing it. Generative AI is necessarily stochastic and unreliable. This is not a flaw to be corrected. This is the way "intelligence" that's like ours tends to work. If you want to handle nuances of natural language you need a fuzzy machine that tries to emulate the human brain. If you want deterministic reliability you should still use a deterministic computer. Humans have long been supplementing our own unreliable wetware with machines (or the application of formal systems like maths and logic) when we want accuracy and predictability. We don't rely on one-shotting gut instinct. We invent maths. And meticulously execute formal algorithms, with pencil and paper if necessary.
If we really want the reliability of deterministic computing as we have come to understand it, we are still going to need to program it formally, and that means we are going to have to communicate our requirements in clear, unambiguous ways. With formal notations with known and understood semantics. Sometimes we'll communicate informally with a language model to help us write these formal requirement. But sometimes we humans will still have to learn and understand enough of a formal way of thinking in order to instruct the formal parts of the computing we need.
So as everyone else turns to vibe coding, I am turning my attention to formality. To type systems, to formal specifications. To domain specific languages. Not because I reject AI coding assistance - in fact I love it - but because I think these will be the obvious complements of LLMs in our future developer toolkit.
Example
At this point, I'm sure you really want some kind of concrete example or demonstration. Well, we are in very early days of this. It's an exciting time when we can experiment with mixing LLMs and vibe coding, with more rigorous and formal programming techniques. I don't believe I or anyone else currently knows how this is going to pan out.
What I'll show you is what I'm playing around with at the moment. In one sense this is very trivial. At the same time, it smells to me like one of those things that's incredibly simple but also incredibly powerful.
I have a tiny domain specific language (DSL) I created to represent UI layouts. (This is mainly intended for describing the control panels in my "world of cake" project, a framework for creating music software)
This DSL uses different kinds of brackets to represent different layouts orientations. Square for vertical, angle for horizontal. (There's some other stuff too we won't go into here.)
For example, a simple panel might be written as :
[<a [b <c1 c2 c3>]> <d [e f]>]
The DSL compiler can turn that into a Jinja2 template for the web.
<div class="column">
<div class="row">
<div id="a">{{ a }}</div>
<div class="column">
<div id="b">{{ b }}</div>
<div class="row">
<div id="c1">{{ c1 }}</div>
<div id="c2">{{ c2 }}</div>
<div id="c3">{{ c3 }}</div>
</div>
</div>
</div>
<div class="row">
<div id="d">{{ d }}</div>
<div class="column">
<div id="e">{{ e }}</div>
<div id="f">{{ f }}</div>
</div>
</div>
</div>
Or a Haxe class laying out a number of boxes.
class UIViewGraph {
private var root:IBox;
private var leaves:Map<String, FixedBox>;
public function new(padding:Float = 10, margin:Float = 10) {
leaves = new Map<String, FixedBox>();
var box_a_14066 = new FixedBox(50, 30, "A");
var box_b_14067 = new FixedBox(50, 30, "B");
var box_c1_14068 = new FixedBox(50, 30, "C1");
var box_c2_14069 = new FixedBox(50, 30, "C2");
var box_c3_14070 = new FixedBox(50, 30, "C3");
var box_d_14074 = new FixedBox(50, 30, "D");
var box_e_14075 = new FixedBox(50, 30, "E");
var box_f_14076 = new FixedBox(50, 30, "F");
var vcol14079 = new VLayout(10, 10);
var hrow14073 = new HLayout(10, 10);
var vcol14072 = new VLayout(10, 10);
var hrow14071 = new HLayout(10, 10);
var hrow14078 = new HLayout(10, 10);
var vcol14077 = new VLayout(10, 10);
vcol14079.addChild(hrow14073);
hrow14073.addChild(box_a_14066);
hrow14073.addChild(vcol14072);
vcol14072.addChild(box_b_14067);
vcol14072.addChild(hrow14071);
hrow14071.addChild(box_c1_14068);
hrow14071.addChild(box_c2_14069);
hrow14071.addChild(box_c3_14070);
vcol14079.addChild(hrow14078);
hrow14078.addChild(box_d_14074);
hrow14078.addChild(vcol14077);
vcol14077.addChild(box_e_14075);
vcol14077.addChild(box_f_14076);
leaves.set("a", box_a_14066);
leaves.set("b", box_b_14067);
leaves.set("c1", box_c1_14068);
leaves.set("c2", box_c2_14069);
leaves.set("c3", box_c3_14070);
leaves.set("d", box_d_14074);
leaves.set("e", box_e_14075);
leaves.set("f", box_f_14076);
LayoutEngine.layout(vcol14079);
this.root = vcol14079;
}
public function getRoot():IBox { return root; }
public function getLeaves():Map<String, FixedBox> { return leaves; }
}
That gets drawn as this :

JS Canvas rendering of nested boxes from Haxe code compiled to JS.
Now there's nothing very new about using small DSLs like this. These have been the building blocks of computer science since its inception. We all know that code generation is a wonderful force multiplier in our field. Allowing us to say a lot, with high precision and confidence, in a very little.
The problem with most code-generation is how to fit it into the development workflow. At best it adds various extra stages to the compile pipeline which need to be managed. At worst, it founders on the problem of "the round trip" ie. once you've started making changes to the target output from a code-generator, what do you do if you want to change the input notation? Jack Herrington, author of Code Generation in Action, used to say that you needed to plan to either throw away the input source to a code-generator. Or the target output. Because trying to map the changes back from the output code to the input code is either very hard or impossible.
And that's why people typically give up on code-generation. You only want to work in languages that are malleable and open to continuous reworking.
Bigger Picture
But AI assistance changes these equations. It just threw our existing development toolkit and workflows up in the air and we are currently reassembling a whole new development process.
The two standard parts of that are
the chat dialogue between developer and a language model. And
the Model-Context Protocol that puts a number of tools and resources at the service of the AI.
MCP seems the most obvious and straightforward way to integrate the use of DSLs into our new development process.
So, my little DSL above is not just a stand-alone program. What I'm building is a generic MCP server for DSLs. It's all written in Clojure with Instaparse, which is a great language for parsers and compilers. And DSLs are written as plugins to be dynamically loaded. Thus we can then provide our local development environment, ie. "coding agent", with a whole "Swiss Army Knife" of handy little-languages like the one described above, directly available to the AI via MCP. This allows the coding agent to offload a large chunk of boilerplate generation to the DSL / code-generator.
In principle it "reduces the load" on the LLM (in terms of complexity). It gives me, a human, an unambiguous and concise way to tell the LLM exactly what I want. Rather than saying stuff like "Can I have the X panel alongside the Y panel, and the Z panel underneath them?", I can write [<X Y> Z]
and there's no stochasticity involved in getting that requirement into the code.
But it also solves the problem of how to integrate all these languages into my development process. The MCP server is a standalone JAR file I can run on my local machine. I (or anyone else) can quickly add new DSLs for new tasks. (And, yes, there's already a DSL for making DSL templates. Ie. pass it a name, a description and an Instaparse grammar and it will return the boilerplate of the files for a new DSL based on it. It's not slick enough yet, but we are getting towards this bootstrapping.) The new development pipeline is the coding agent calling out to MCP tools. So once my server is running, it's as simple as telling the AI what port number to look at, for all the DSLs to be available in the development process.
Now something which is still very tentative - it's still very early days of my building and experimenting with this setup, and I need to test in many more situations with many more DSLs. But my hunch / hope is that we can even improve the "round trip" situation. The output of the DSLs is code. But code intended for the coding assistant AI to slot into the main codebase. So what happens when we need to change some large scale architectural aspect of our design that was originally given in the form of a snippet of DSL? Well, we can update that snippet and ask the AI to get the expanded target code again. And then it has to integrate the new shape of that architecture back into the code-base, keeping the extra modifications that were made to it.
Will the LLM be any good at this? That remains to be seen. It works in the small, in the experiments I've been doing. But to be honest, at this point, the tasks are still so small that the AI would handle them fine without needing to use the DSL at all. As I start to scale up, I'll get a better sense whether the AI is helpful here.
In Conclusion
If you got this far, thank you. Here's the take-home.
"If a task can be done by generative AI, it didn't need doing in the first place"
I love language models. I love chat as UI to computers. I love generative AI. But I'm here to tell us to stop using it.
70% of what can be done by generative AI is bullshit handicap signalling. And humans can and should just cure ourselves of doing it
The other 30% is redundant boilerplate in our tech. stacks. Which can be squeezed out of both human and AI generative practice, and pushed to more efficient and reliable traditional computing. As I see it, figuring out how to do that last part is the most exciting research I can be doing around AI right now.
My vision of the Future of Coding isn't particularly new or radical. It's been in the Lisp, and especially Scheme / Racket, communities forever. It's sometimes known as "Language Oriented Programming". We build applications out of a tower of low-redundancy, highly expressive, specialised little-languages (DSLs). And we make it easy to create such languages. And to integrate multiple languages together in our development environment and development process.
The rise of LLM coding assistance and "vibe coding" doesn't sideline that tradition. In fact, I believe, they are great complements. With DSLs we can capture the formality we still need to use to express constraints on the behaviour of stochastic coding agents. While the emerging toolkit of chat plus MCP gives us a new and very accessible way to bring the ideas from LOP into the emerging mainstream development practice.
DSLs can ensure that software built with the help of coding AIs can be as robust and reliable as it needs to be. We'll use fewer tokens and energy if we delegate more of the "grunt-work" of code-generation to DSLs. Meanwhile we can reserve generative AI for the tasks where it really shines : handling ambiguities and fuzziness, helping humans navigate large code-bases or untangle conceptual complexities etc.