(democratizing software)
democracy [dɪˈmɒk.ɹə.si] (noun): power of the people
On November 21st, 2024, (I gave a talk about the software crisis), detailing its general impact on how we learn and practice the craft of software. It was equal parts philosophical and technical, with a focus on human-scale problems and how they affect networks of craftspeople and learners. The central thesis of these talks was a simple message: something has to change, here's one direction we can go. The aim wasn't to shame others into submission, but rather to inspire people to start closely examining their craft under a new technical and philosophical lens. When presenting a new method of anything, be it computation, communication, thought, or something that covers all three, alienating people is a great way to kill adoption. We have to connect with people, and meet them where they are. We're all speaking the same language, just different dialects. We must travel and traverse uneven terrain: there are amazing cultures there. To that end, in this talk, I proposed a common language for communication and computation: rewriting. Out of all of the paradigms I surveyed, all of the different models of and methods of describing thinking about computation, this happened to be the one I found most universally intuitive. This talk was born of that research, utilizing my background in education and my connections with friends and family. If we want to stop feeling like we're aliens to each-other, we must first find a way to communicate. I hope you enjoy the talk, and if it resonates with you, reach out. I'd love to hear from you.
------- Democratizing Software
Good morning! My name is Wryl, and today I'm going to talk to you about something near and dear to my heart. The title of this talk is Democratizing Software, and this talk is the culmination of research, observations and principles I've cultivated through my journey with programming. It'll have ups, it'll have downs, it'll have some new concepts, it'll have some old concepts, but my primary goal is this: show how we can democratize software. I want to enable people of all backgrounds to participate in the beauty of computation.
Software As Mountains
But first, we have to talk about some difficult things. We have to talk about how difficult it is to participate in the craft of software, and how it's akin to scaling mountains of concepts and abstractions. We, as mountaineers, take for granted how easily we can traverse the terrain. We tolerate so much, because it’s the only way we can get from point A to point B. And personally, I’m very, very tired of hiking around. Imagine how newcomers feel?
A curious traveler arrives at the base of a mountain. An experienced, yet weary, mountaineer sits nearby. The energetic traveler asks the mountaineer: 'Where should I start?' The mountaineer looks around for a few moments, before responding: 'You should start where your feet touch the ground.'
This is a common parable. Newcomers and curious folk come to us and ask: 'Where should I start?', and we'll respond with something like Python, or JavaScript, or defer and say 'Whatever you can get your hands on.' Instead of pointing at some well-trodden paths, we ask them to survey the mountain range. To blaze their own trails, navigate the terrain on your own. A lot of people get lost. And sometimes, they don’t come back.
The mountains are tall, and are growing taller. They twist and turn in impossible ways. The architecture here is hostile to life.
And the reason they get lost is because of the effort required to scale these mountains. The terrain here is hazardous, and often makes no navigational sense. Stacking concepts and abstractions on top of one another means that each layer is a new concept to be understood and internalized. We also don't have the limitations that real mountains do: we can build arbitrarily nested structures, things that contain themselves, layers and layers of indirection. I don’t think we know how many people we’ve lost to this.
Curious people can't understand what we do. Software is getting harder to construct. Complexity is out of control. There are no easy trails to travel.
We're setting ourselves up to look like aliens, as curious individuals can't really understand what we do at a glance, if at all. They need a guide to help them up and down the mountain. This is also bleeding into how we're writing software,as eventually, we'll end up writing things that we can't comprehend fully, only partially. Blind spots can and will develop. The trails will disappear, overrun with brush. When you run out of energy to climb, nature and gravity take over in the worst ways.
Climbing up and down mountains requires tolerance. Tolerance requires energy. Age, disability, and the flow of life drain energy. Energy is a rare resource.
And this compounds in so many different, unpredictable ways. Our lives are filled with certain flavors of chaos. We may not be able to comprehend what other people, practicing our common craft, are doing. Context switching, emergencies, and the general flow of life drain what little energy we might have for crafting software. And it’s very, very difficult to recover that energy, and gear up for another climb. Look at how burnout develops, and how long it takes to learn how to walk again, let alone climb the mountains we’re required to climb.
We can build mountains faster than we can climb then. We are optimizing for exclusion and alienation. Computing is a human activity, yet we have made it inhumane.
We cannot keep pace, even as experienced mountaineers. I fear we're hitting the limit. I certainly can't keep up anymore, even though I’ve tried. New mountains appear every day, and we even have tools that generate new mountains for you, on-demand! We're getting to the point that two people, studying the same craft, can't talk to each other, or relate what they're working on to their friends and family. We've turned something that is deeply, deeply human into something bordering inhumane.
How do we build humane languages? How do we build humane tools? How do we flatten these mountains?
What do we do about it? How do we even begin to clean any of this up? How do we tear the mountains, the scaffolding, and all the unneeded complexity down, while staying in touch with basic intuition? How do we do this without sacrificing the last 60 years of advancements? How do we build tools that can withstand chaos?
Intuition And Exploration
To even start, we need to talk about how humans learn explore, and internalize new things. We also need to talk about how they maintain and update that knowledge. We need to have a good, solid grasp on how knowledge is constructed, and how much concepts weigh in our minds. We have to really understand how much conceptual weight we have piled up in our heads.
Learning is seen as the layering of concepts. Crafting software is seen as the layering of abstractions. This is how mountains are built.
Learning is often seen as the process of layering new, isolated concepts on top of each-other in the hope that they'll interlock. Once everything 'clicks' into place, you'll gain a greater understanding and intuition about things, right? That’s the hope, anyway. We follow the same pattern in constructing software, layering abstractions on top of eachother in the hope that they'll click together. But it's all the same: layering is how mountains are built.
Learning is the development of new intuition. New intuition can only be developed in terms of old intuition. Intuitive concepts compose with themselves.
In my observations, learning is less about layered, isolated, interlocking concepts, and more about connecting with pre-existing intuition from all over a person's mind. Hobbies, habits, activities, interests: they’re all great, strong candidates. If someone loves to cook, I can connect algorithms to recipes. The more prominent and strong the intuition, the more it can be used as a basis for new concepts. Learning is less layering, and more web weaving: it's all the same cognitive material, just woven in different patterns.
To learn is to explore and be tolerant of failure. Why are our tools intolerant of failure? Why do our tools not allow us to explore?
To that end, this weaving of intuition can happen in a number of different ways, each of them individualized and deeply personal. Learners have to be able to explore how they can weave their own intuition. We can offer a nurturing environment, and give them a push, but they need to be able to explore the space of possibilities. They need to be able to rewind and fast forward, to pick apart alternatives, to examine and internalize cause and effect. They need to be able to play. A grand majority of tools we hand to beginners can’t meet these requirements.
Our craft is currently driven by historical inertia. We do things because we've always done them. We have to break from this in drastic ways.
And it's not because it's an inherent flaw of software! It's because of the force of historical inertia. These are the models we've used, this is how we teach software development, this is how we teach computation, this is how we make a living from it, and nothing can change. Ever. It's frowned upon. Which I think is absolutely wrong! We need to break this cycle of inertia, else we'll continue to be swept up in it.
Growth And Mastery
To break the cycle, we need to look into how we lost pace with our tools, and how the 'old masters' developed their intuition. I'm of the opinion that something went very, very wrong in the past 40 years, and it's been largely unaddressed, despite it being pervasive and visible. You'll hear whispers of it in retrocomputing and low-level programming circles. It's as if it's forbidden, arcane knowledge. It’s not. It’s just been forgotten.
Growth of a platform is measured in terms of power and complexity. Mastery of a platform is measured by effective use of its power and complexity. The growth of a platform, and the time it takes to master it, have diverged.
There's this idea of growth versus mastery. The idea that, given some set of tools or resources, it takes some time to fully master their usage, to effectively utilize those tools and resources. We see this everywhere. It’s why the demoscene is impressive, and why developers on older console generations shipped more technically impressive games at the end of a console’s lifecycle. They mastered the platform. It may be days, weeks, months or years, but mastery can be attained! But if the tools keep growing in complexity, in features, and the criteria for mastery keeps getting larger, it becomes impossible to keep up.
If the path to mastery is difficult, many people won't follow it. This leaves knowledge and mentorship gaps. The craft breaks down and fragments.
It's gotten so much harder. Fewer people understand, totally, what a given system does. Fewer masters emerge, and the flow of knowledge is broken. This leads to mentorship gaps, and large institutions, be it universities or companies, rarely have a solution to this problem. It sits unaddressed, and leaves everyone feeling some flavor of worthless. Crafting software becomes incredibly painful. And we often try to avoid pain.
To solve this problem, we must build shorter paths to mastery. There's been a lot of work in this space. Counterculture is thriving.

Retrocomputing
Permacomputing
Educational Languages
Minimalist Computing
Low-Level Programming
We seek comfort in counterculture. I'd argue that Handmade is a form of counterculture: we're trying to ease the pain of practicing our craft. It's comforting, because here, the growth and mastery curves align. It's so satisfying to see something work, while having a complete understanding of it. It's empowering! And counterculture should be empowering. But we should take a critical view on the solutions that counterculture generates. Because there are no silver bullets, but there might be some really good alloys.
Some of these approaches have some significant problems. They often sacrifice flexibility for an easy path to mastery. If the mountains are small, they are easy to climb.
Often, we shrink the mountains, but lose the ecosystems that are thriving on their slopes. We lose things like office suites, web browsers, and the standards of interoperability that we've come to expect of the systems we build. Mastery that's easy to obtain, but ineffective once obtained, sometimes stings. We're reminded that the 'real' mountains exist, and that we still need to climb them. It’s hard not to feel discouraged. You’re still doing real, useful work, though. That never changes.
We can try a new computing model, or a new programming paradigm. What could any person understand at a glance? What could 'natural' computing look like?
How can we make counterculture work? What if we switched things up and focused on cultivating intuition as the basis for a computing model? What if we had our cake and ate it too? How can we give any individual, regardless of where they are in their computing journey, tools that are universal and easy to understand? What would it look like? What form would it take? How far could we get?
A Common Language
Turns out, there exists a model that may check all the desirable boxes we’re concerned about. It’s beautifully symmetric, easy to implement, and provides a basis for a common language for communication of computing concepts. It's called rewriting. Much of my work in the past eight years has been focused around rewriting, and I've come to the conclusion that it is a perfect candidate solution for the problems that I’ve been talking about. It takes many forms, and has many interpretations, but the basics are very easy to grasp.
Rewriting is an under-explored computing paradigm. It is simple, self-evident, and provides many technical advantages. It is a candidate for a 'universally understood' method of computing.
It's also woefully under-explored and offers some very beneficial technical aspects, some of which are very hard to obtain in traditional languages and paradigms, and doing so is often a research topics in its own right. In contrast, rewriting can be explained in a few minutes. It also offers a clear path to mastery, as there are very few moving parts, both implementation-wise and conceptually. However, some developers seem to struggle a bit with it, trying to search for where the complexity, seemingly, got shifted to. We’ll cover that.
Two concepts are required to understand rewriting: structure, and rules. Rewriting languages commonly operate on one single, global structure. Rules tell you how to incrementally transform the structure.
But first, I need to introduce you to the fundamentals. There are two fundamental concepts in rewriting: structures, and rules. Structures are the 'medium' in which you encode your data, and there's typically just one, single, global structure to act on. You can treat this global structure as your 'main memory', while rules are code that transforms a part of the structure in some way, guided by a form of pattern matching. Everything we need can be built on top of these two concepts.
Structures can be anything: strings, trees, multisets/bags, etc. Rules are pairs: (pattern -> replacement). Rule evaluation strategies can vary: random, priority-based, ordered, longest match, etc.
Structures can be any datastructure you're familiar with: strings, trees, sets, multisets, maps, etc. Rules are equally simple: they tell you what to search for in your structure of choice, and they tell you what to replace it with. Rules always search for a sub-structure, or pattern, in the global structure. If you've ever used find and replace in your text editor of choice, you're using string rewriting rules! We have a ton of ways to handle the matching of rules. I can pick a random rule from the ones that matched, pick the first rule that matched, or pick the rule with the most conditions that matched. We have a lot of options here!
My work focuses on multiset/bag rewriting with priority-based rules. A multiset is an unordered collection of things that allows duplicates. These structures are everywhere!
I'd like to narrow my focus to multiset rewriting, as that's what I've been researching for the past few years. I’m also going to treat rules that match first in the rule list as ones that should 'win out' when we have multiple rules that match. A multiset is a fancy name for a bag of things. Any bag of items is a multiset. Bags don't have any order to them, and can have duplicate items. Turns out, these structures pop up everywhere, in places you don't really think about, and have some really, really useful properties!
A cart full of groceries. A to-do list. Human memory?
Think about every random scribbling or unordered list you've ever written. Think hard, because a lot of things might qualify as unordered bags of items! Grocery lists, to-do lists, ingredient lists. You'll start seeing them everywhere, eventually!
This is the most intuitive method of computing I've found. A surprising number of things are rewriting programs! If you understand cause and effect, you can understand rewriting.
And not only that, several processes that occur naturally are able to be written in terms of rewriting. The passing of time, the changing of the seasons, the cycle of crops, the act of cooking, the process of crafting and manufacturing. Everything involves some before and after, cause and effect, things to consume and produce. Which means that we can connect with a lot of baseline intuition that a lot of newcomers are already equipped with!
Apples, Oranges, Cherries
↓
Fruit Salad
This is a simple rewrite rule. It searches the bag for 3 things: apples, oranges, and cherries. If it finds all of them, the rule will match, and we’ll remove apples, oranges and cherries from the bag. We’ll then put a fruit salad back into the bag. We need all of the ingredients, all of the conditions, above the arrow, to match a rule. If you can understand this, well done! You’ve seen your first rewriting program. This is our 'Hello, world!'.
Do Chores
↓
Do Laundry, Do Dishes, Sweep Floors

Laundry Done, Dishes Done, Floors Swept
↓
Chores Done
We can have multiple rules. Here's a simple to-do list. The first rule searches the bag for a 'Do Chores' item, and if it finds one, it replaces it with 'Do Laundry', 'Do Dishes', and 'Sweep Floors'. The second rule searches for a 'Laundry Done' item, a 'Dishes Done' item, a 'Floors Swept' item, and replaces them with a 'Chores Done' item. It reads nicely, doesn't it? Here's the chores I have to do. Here's how I know all of my chores have been done. 'If I've done the laundry, washed the dishes, and swept the floors, I'm done with my chores.'
Main Loop
↓
Clear The Screen,
Poll For Input,
Process Game Entities,
Draw World,
Draw Entities,
Main Loop
This looks familiar, doesn't it? A simple game loop. Now we're talking! This doesn't look like a rewriting rule, does it? It just looks like some kind of procedure, some kind of function definition. That's not an accident: we get procedures for free with rewriting rules. They're a universal control flow structure! We can write in something that is, effectively, pseudocode, and have it mean what we intended it to mean. By the way, this is a real piece of sample code! It’s a common loop I include in some rewriting programs that use graphics and input.
It’s [condition] outside,
I don’t like [condition] days
↓
I’ll stay inside today.


It’s rainy outside.
I don’t like rainy days.
I don’t like cold days.
Let's get a bit weirder. What if the items in our bag were lists of words? What if we added the ability to add wildcards or variables to our patterns? Well, we gain the ability to write more general rules! Here's a simple tool to help you navigate the weather, relative to your preferences. The rules are above, and the initial state of the bag is below. We start off with 3 items in the bag, telling us that it’s rainy outside, we don’t like rainy days, and we don’t like cold days. The highlighted words represent 'holes' in the pattern: variables that can take on any value. What happens if we try to fire this rule?
It’s rainy outside,
I don’t like rainy days
↓
I’ll stay inside today.


It’s rainy outside.
I don’t like rainy days.
I don’t like cold days.
Looks like this rule matched! We searched through the bag, found 'It's rainy outside', searched even further and found 'I don't like rainy days', and consumed them both! We replaced them with 'I'll stay inside today', and because nothing else matched, we stopped! Simple, right? But how can we take this further, to something like game logic?
The player is at 10 10.
An enemy is at 10 20.
An enemy has A sword.
A sword is conductive.
Lightning struck 10 20.

Lightning deals 30 damage.
What if I have some complex information I want to throw into my bag? Say I'm writing a roguelike, and have some pieces of information that are relevant to my game. I can write them in a very straight-forward manner, something that resembles note-taking rather than defining data. Here, I'm constructing a very small game world: one where entities exist at (X, Y) coordinates, can have weapons, and can get struck by lightning if they're holding a conductive weapon. Notice the absence of things like classes, or types. I just wrote down what I knew I wanted in my game world. I conjured data from nothing.
Lightning struck [a] [place],
[A] [thing] is at [a] [place]?
[A] [thing] has [Some] [item]?
[Some] [item] is conductive?
↓
[A] [thing] is struck by Lightning



[A] [thing] is struck by [Striker],
[Striker] deals [damage] damage?
↓
[A] [thing] is hit for [damage] damage
And I can write some pretty straight-forward logic that handles those lightning strikes I want in my game world, including a general rule that looks up and applies the appropriate damage value. This is pretty self-evident, right? I'm assembling what I need to do some useful work immediately as it’s required. I don’t have to care about classes, methods, attributes, types, or anything conceptually heavy. What happens if we run these rules?
Lightning struck 10 20,
An enemy is at 10 20?
An enemy has A sword?
A sword is conductive?
↓
An enemy is struck by Lightning



An enemy is struck by Lightning,
Lightning deals 30 damage?
↓
An enemy is hit for 30 damage
Looks like the enemy we defined earlier got struck by lightning, because they were carrying a sword, which is conductive. They were then hit for some damage after the strike. It’s important to note the sequence of events here. Every time we successfully find a rule that matches, we 'fire' the rule, consuming everything above the arrow, and producing things below the arrow. We then go back to the first rule in the list, and start checking for matches from there. So the first rule matched, which consumed the information about the lightning strike, and then the first rule failed to match. The second rule is able to match, and deals the damage to the enemy.
Grimoire (Video Demonstration)
It’s not like this is some kind of hypothetical system either. This is Grimoire, an interactive fiction system written by one of my team members, Yumaikas. Underneath, it’s utilizing rewriting rules to govern transitions in the story, relative to choices the player makes. What’s really cool is how slim this is. Yumaikas had the idea and implemented it in a matter of hours. They were able to get off and running because rewriting lends itself well to small, simple interpreters and compilers. If you’re familiar with Twine, this is incredibly similar. And it’s super cool! The source is also very, very small for what it does! The semantics compress incredibly well.
Rewriting isn't just textual.
The representation of rewriting rules doesn’t need to be strictly tied to text, or any kind of specific encoding or language. In fact, we can build programs in rewriting languages without having to touch text at all! We can use hand drawn symbols instead, making the resulting language pictographic! And all of the magic is preserved, if not enhanced.
Tote
(Farming) (Video Demonstration)
This is an example Tote program. It depicts a farmer, tending their field according to the cycles of harvests. The rewriting rules model the passage of time, the growth of crops, the farmer’s harvest of the crops, and the growth of the farmer’s field over time. This isn’t just an algorithm. This is describing a real world process! It doesn’t just describe a computation, it conveys an idea! There’s some incredible power here. And it’s because of all of this beautiful symmetry that rewriting languages give you. I have never seen this in another model of computation. Ever.
Tote
(Tic-Tac-Toe) (Video Demonstration)
I don’t know about you, but when I saw that working, it was beyond magical. It was proof of life. It was like watching actual magic. The tool also supports backwards and forward execution now. Tote was developed by Devine of Hundred Rabbits over the course of about a week after I described my plans for an IDE for rewriting. Imagine that. An IDE in a week, running tic-tac-toe.
These are real programs.
These are real programs. They run on your computer. They share the same underlying model of computation. I can take a program I’ve written, import it into Tote, and experiment to my heart’s content. I can export rules I’ve written from Tote to a textual format, and do crazy things like compile them to native code, or run them in an interpreter, or embed them in my application. This is a fraction of what’s possible.
Nova
To show you what’s possible, I’d like to introduce you to Nova. Nova is a lightweight language for conversing with computers. It is a programming language, a data format, a computing stack, and, I hope, the technical portion of the solution for the problems I’ve discussed today.
Nova is my attempt at an understandable computing system. It is a lightweight language for conversing with computers. It bridges communication gaps.
Nova is my attempt at an understandable computing System. It is intended to be conceptually light-weight, but flexible and malleable enough to fit into any situation where communication gaps exist. Nova can serve your primary programming language, as a note format, as a method for conveying ideas, and as a sketching tool for software. It is based on rewriting, and you’ve seen some examples of it earlier in the talk. It unifies several threads of research, some of which dates back to the 1920s, and I’m very excited to finally open up about these threads.
I'm not here to give a language talk. Rewriting is an incredibly powerful paradigm. These ideas are useful on their own.
But first, a disclaimer. I am not here to promote my own language. I’m not here to just preach about how I have the solution to all of these problems sitting in a repository somewhere. I’m not here to start language wars. I’m here to show you how rewriting can be applied effectively. I’m here to show you how I’m applying it to my project and what it can really do. Rewriting is incredibly powerful. I’m just the messenger for that idea, because these concepts have been locked behind impenetrable walls for decades. They deserve to be brought into the light.
The only way to represent data in Nova is by phrasing it in terms of facts. Facts are stored in a single, global bag, called the knowledge base. Rules search for facts, consume what they find, and produce new facts.
Nova is a rewriting language. Nova performs rewriting over a bag of variable-length tuples. We call this bag the 'knowledge base', and we call the tuples in the bag 'facts'. Like we covered earlier, Nova rules search for, and consume, items in the knowledge according to patterns. These patterns are simple and can contain variables that are bound to values when searching through the knowledge base. Your only method for specifying any kind of data is by writing down facts, and your only method for writing any code is by writing rules that consume and produce facts.
The sky is blue.
The player has a sword.
You are in the foyer.

It is dark,
You are likely to be eaten by a Thue.
Here are some example facts. Let's shed some light on this.
Nova utilizes natural language by relaxing parsing standards. Nova models state in a way that isn't intimidating: it's just an evolving bag of facts. There's no nesting, scopes or special cases. Just rules and facts.
The previous slide was example code. Simple, whitespace delimited tokens are all we really need to pull the magic off. It’s a simple touch, but because we’re not doing any kind of natural language parsing, it makes implementing Nova so much easier. Maintaining the balance between easy and simple is key. I have tried to design it such that beginners can pick it up and run. Having a low amount of conceptual dependencies is crucial. Newcomers don’t need to understand nesting, scopes, or any special cases or interactions between different language features. It’s just rules and facts, flat and simple.
This is surprisingly easy for beginners to understand. Newcomers don't have to care about managing cross-cutting concerns. Complex ideas can be expressed with very little effort.
And while the implementation is simple, the leverage it gives you is much greater than you’d imagine. Things that would necessitate something like an ECS or a textbook on data-driven design and event sourcing take just a few lines in Nova. I don’t have to think in terms of arrays, lists, or any structures that aren’t explicitly required to allow me to express my intent. I can model complex logic, cross-cutting concerns, and corner cases that would be time consuming, and buggy, to model with traditional methods. Let’s look at an example.
You are in The Foyer.
The Foyer is West of The Kitchen.
The Foyer is East of The Den.
The Garden is South of The Den.

North is the opposite of South.
South is the opposite of North.
East is the opposite of West.
West is the opposite of East.

Move East.
Let’s build a simple text adventure movement system, starting with some sample locations, and a starting location. Let’s also define some useful information about directions, like 'north is the opposite of south'. A reminder, this is sample code. This is actual data that we can manipulate. This is our initial state of our program. Let’s see what the code to handle movement looks like.
You are in [A] [Place],
Move [direction],
[Adjacent] [Room] is [direction] of [A] [Place]?
↓
You are in [Adjacent] [Room]


You are in [A] [Place],
Move [Direction],
[Opposite] is the opposite of [Direction]?
[Adjacent] [Room] is [Opposite] of [A] [Place]?
↓
You are in [Adjacent] [Room]
What do these two rules say? If we’re in a place, and we move a direction, and there’s a place to move to that’s in that direction, we’re now standing in the adjacent place. OR. If we’re in a place, and we move a direction, and there’s a place to move that has a connection to the place I’m at, but in the opposite direction, we’re now standing in the adjacent place. I only have to specify one connection between two places, and these rules will detect a move, and the connection, and move me around the game world. What happens if we run it?
You are in The Foyer,
Move East,
West is the opposite of East?
The Kitchen is West of The Foyer?
↓
You are in The Kitchen
Looks like the second rule was the only one that matched. Which is great! We’re in the foyer, we try to move east, the kitchen is west of the foyer, and west is the opposite of east, therefore there’s a connection between the foyer and the kitchen. And because that rule fired, we’re now sitting in the kitchen. I can imagine the equivalent code in C, Python, or another language. It’s not that complex, but forces you to encode your thoughts into a new format that the computer can run. Wouldn’t it be nice if we could just write what we meant?
image smile 0 0 255 255 255
image smile 3 0 255 255 255
image smile 0 2 255 255 255
image smile 3 2 255 255 255
image smile 1 3 255 255 255
image smile 2 3 255 255 255
As another example, here’s a little image of a smiley face, encoded as some Nova facts. We have X/Y coordinates, some RGB values, and an image name. Let’s say, for example, I wanted to generate a palette from this image. I want a collection of all the colors in the image, and I want to give them unique names or identifiers so I can use them later. What would the rules look like?
image [name] [x] [y] [r] [g] [b],
palette [color] [r] [g] [b]?
↓
image [name] [x] [y] [color]



image [name] [x] [y] [r] [g] [b]
↓
image [name] [x] [y] [color]
palette [color] [r] [g] [b]
If I have a pixel in an image with some RGB value, and this RGB value is already in the palette, replace the RGB value with the name of the color in the palette. If I have a pixel in an image with some RGB value, and nothing else, generate a brand new palette entry with that RGB value, and replace the RGB value at that pixel with the name of the color in the palette. Notice, in the second rule, how “color” wasn’t featured on the left-hand side. If you use an unbound variable, we’ll just generate a unique value for you. This is incredibly useful, if not crucial, for things like games. Just spawn a game entity, slap an ID on it and go!
The mouse button was pressed,
The cursor is above [A] [button]?
[A] [button] triggers [Action]?
↓
[Action]



Quit Button is at 20 20,
Quit Button triggers Quit
How about some simple input handling? I’m pretty sure this is self-explanatory. I assume some rules exist that grab mouse input, do some rudimentary bounding box math, and handle some actions. Super versatile, super simple, and can even act as a kind of configuration language that can either be incorporated into your build process, or into running code.
Nova is simple to implement. It has a tiny core. With few moving parts, you get..

Transactional Memory
Time-Travel Debugging
Network/Storage Transparency
Natural Language Programming
A Universal Glue
What does all of this buy you? Well.. rewriting languages model time in terms of rewrite steps, right? Find which rules match, apply rules, repeat. That means that if you stop the execution at any point, you can take the knowledge base, throw it on another computer, and resume execution. Rewriting is natively reentrant, which buys you a ton of newfound power. Each rewriting step is like a database transaction: read, modify, write. It doesn’t matter if it’s in memory, on an SSD or spinning rust, or on another computer: everything is transparent to rewriting if you want it to be. Each of these things, individually, is a research project in and of itself. But we get these properties for free with a simple model switch!
Nova is both compiled and interpreted. It is possible to compile Nova code to your language of choice. Embedding Nova allows you to communicate with people that are not developers.
Nova can function as an independent language, or as a way to augment existing languages. I can take a piece of Nova code and compile it to assembly, C, Go, Java, JavaScript, you name it. Imagine being able to hand someone a note file, integrate it into your build system, and collaborate with people that don’t use your particular programming language, but can now express computations and ideas in a common, portable format. This is a huge win for things like game studios, small businesses, and people who just want to write simple, easy to understand tools for themselves that are self evident.
Nova is not a strictly educational language, and yet it's easy to learn. It's a crafting material. It's malleable and adaptable, and stays with you as you grow.
I didn’t design this as an educational language, but it fits the bill. With educational languages, there’s a common thread: you can’t carry them with you throughout your programming career. MIT’s Scratch and Seymour Papert’s Logo can’t fit into the places we need them to without some heavy effort. Newcomers have to graduate to new, “real” languages to continue their journey. And I don’t think that’s fair to them. We need to build tools that can evolve and persist as people grow and explore. If we don’t, we’re just setting them up for constant context switching, and giving them conceptual dependency graphs that rival some of the worst NPM packages.
Potters do not outgrow clay.
We’re craftspeople. We need to start acting like them. We need to acknowledge our relation to other crafts and trades, and ask 'do we really have the right mindset?' If we don’t, we’ll lose sight of the ground. We’ll lose sight of what it means to be a trade. We can’t keep this process of discarding skills and tools and methods up. It’s just not tenable. Potters do not outgrow clay. It is crucial to their craft.
Closing Thoughts
I’m really grateful to be presenting this at Handmade. I feel like we’re at a tipping point, where people are genuinely fed up with the status quo. We’re sick of seeing our craft exploited, our dreams grifted, and our hopes for the future dashed by another social problem peddling a technical solution. And it’s here I feel the most difference can be made. Something has to change. And it’ll come in two parts, technical and social. Our craft will change. It has to, in order to survive. We have to become proper stewards from all angles. With that, some closing thoughts.
Building bridges without the right material is hard.
Bret Victor’s presentations motivated me heavily when I was younger, but as I got older, I realized that he was using the same material as everyone else. He showed that we didn’t have to keep thinking of programs as opaque boxes that were constructed by roleplaying as a computer. But even then, he was limited by things like JavaScipt. When you’re fighting your language, or your computing model, everything is incredibly hard. You are fighting the forces of nature and you will lose. The only solution is a change in model, in perspective, and in principle, usually all at once. We have to find symmetry and balance in the things we build and use.
The future is hidden in the past. Tear down all the scaffolding. Connect with people.
This work didn’t happen in a vacuum. I’ve spent the last 8 years digging up old papers and concepts, searching for fragmented pieces of knowledge via the Internet Archive, and trying to piece together the past that’s been forgotten. There’s so much research that didn’t get a capital infusion, and was left to die. Everything in this talk was an aggregation of that research. And in that same spirit, gain some perspective and tear the scaffolding down around your programs. Get rid of unneeded structure, needless complexity. Try to connect with the people around you that aren’t doing what you’re doing. Think about how they’d understand what you’re working on, and if they couldn’t, simplify, simplify, simplify!
Less is more. Don't create impossible architecture. Focus on building intuition.
Doing more with less is one of the major principles I’ve been following, and will continue to follow. Ideas should be multi-function and cover as much ground as possible. This lets you breathe, and avoid falling into cognitive overload. Don’t build factory factory factories. Don’t build things that you can’t physically relate to. Physical engineering has the benefit of having a limiter on the degrees of freedom of anything that’s built, enforced by things like gravity. We don’t have those kind of constraints. Don’t focus on cramming as many concepts in your head as possible. Instead, ask 'I know X, so can I rephrase Y in terms of X?' every time you encounter a new concept. It’ll help you connect new concepts to old ones, and you’ll have an easier time learning.
Keep a foot at sea level. Cultivate confidence in others. You are not alone.
Be careful about assuming what others know about what you’re working on. It’s easier to assume nobody knows what you’re working on, and learn to explain it in simple, but clear, terms. It’ll make you more confident, and you can practice it every day by talking to friends and family. Spread the joy of computing around. And in doing so, you can build people up. You can tell them that they matter, and that it’s possible to have agency over their lives. That if a human understood this once, a human can understand this again. That mastery is achievable, and there’s a ton of other people that want to help them along, should they need it. You can tell them that they aren’t alone. Because we aren’t. We never were. We just thought we were.
Thank you.
Thank you.
⦶ (Tote, by Devine Lu Linvega) (Grimoire, by Yumaikas) (The Nova Wiki) (The Nova Forum) (The Nova Discord) ------- (back)
...... ... .. ... .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ... .. ... ......
wryl © 2025