We Can Have Nice Things

Welcome to Project WitchChant

Huh?

WCHNT is an experimental new programming language.

Its name, WCHNT, is an acronym for "We Can Have Nice Things". Which is what this is all about.

It's pronounced "Witch Chant", because a) that's a hell of a lot more pronounceable than trying to say "WCHNT" (whucn't?), and b) it's a damned cool name for a programming language.

Why?

This is the language I've been thinking about for over 20 years. Since the early 2000s when I first started wondering why programming couldn't be a lot easier than it was.

Since then I've used various programming languages. I've taught the comparative programming languages course at university. And I've had plenty of frustrations working in a number of languages and environments, which all seemed just too much like hard work.

And so, like all programmers, I've been dreaming of my ideal language for a long time. And I've finally got to that stage in life when I have to do the thing.

What?

There are multiple ways of understanding WCHNT, but perhaps the key is something I wrote on Quora back in about 2014 :

It's the mismatch between a program that thinks of itself as a network of interacting objects, and a language that has no concept of "a network of interacting objects", that makes Java and friends so much hard work.

And what I meant by that was that it's obvious that an object oriented program in Java, C++, or even Smalltalk, is a network of interacting objects, but there's actually no way to talk about that network. You define each class in its own isolated little world. And then you have to create and hard-wire the objects together, manually, in imperative code.

That's verbose, error prone work. You might layer a type system on top to add insult to injury by telling you every time you make a mistake. But why can't you just say (and read) in one place, definitively, what all the classes are and how they fit together?

I call that network of classes an "assemblage". And I call this paradigm "assemblage programming". It's not an entirely sui generis paradigm. It's very much a subclass of "object oriented" programming. And WCHNT is definitely conceived of as an OO language. But it's OO "turned inside out". Where everything is organised around the assemblage rather than the individual classes.

Let's get back to the pain of OO. Not only is there nowhere (except scattered around in the type system) any concept of how classes fit together. You have to put the objects together at the right time, in the right order. Otherwise it's another disaster. So then you might come up with design patterns like factories and dependency injection frameworks that tell you how to write the code that makes and wires together the right things at the right time.

But if we know how the objects are meant to fit together we can do this declaratively.

An assemblage, then, is a group of classes designed to work closely together, whose structure and relations are declared together in a single place, which acts as the single source of truth for the schema of data in your program.

And an assemblage programming language, such as WCHNT, turns the traditional structure of an OO program inside out to organise everything around this assemblage.

How does this look in practice?

In WCHNT, we take inspiration from Haskell's data keyword and from Backus-Naur grammars, and write the schema of our assemblage like this

Game = PlayArea Ball
PlayArea = Rect
Rect = Int/x Int/y Int/width Int/height
Ball = Int/x Int/y Int/dx Int/dy Int/rad

This declares four classes, a Game is made of a PlayArea and a Ball. The PlayArea contains a Rect. The Rect consists of four Ints, to which we give custom names of x,y,width and height. While a Ball has x,y,dx,dy and radius.

Note that where there is only one instance variable of a specific type, we don't need to give an explicit name. The name defaults to the lower-cased version of the class name. So we don't have to say something redundant like PlayArea playArea; Inside the Game class there will be a slot called playArea.

This schema is the full declaration of the assemblage. It occurs in a specific section at the beginning of the program. And is the only source of truth for all the classes.

Declarative relationships

In my Quora rant I finished up by saying, "somewhere in the UML is a great idea waiting for a good execution."

Particularly what struck me about UML is the distinctions it makes between the different "has-a" relationships such as "this is a component of that" (and therefore part of the life-cycle of its owner) or "this is an independent object temporarily lent to this object" or "this is some kind of containing context".

But these relationships are not part of the programming language itself. You can generate classes from UML, but the round-trip is error prone. And most people just implement these ideas in an ad hoc way.

In assemblage programming, we make some of these relations explicit, which lets the compiler reason about the relationship down the line.

In addition to the normal composition of classes illustrated above, WCHNT defines four other important relationships between classes which we specify with "sigils" (special symbol prefixes in the schema declaration).

"Context-dependent" components

A context-dependent component is one which is tied to its owner or parent.

Car = :Engine String/colour

In this example, the colon specifies that the Engine is a context-dependent component. It is owned by a car and can only exist inside a car. In practice what this means is that the compiler gives the Engine an instance variable called theCar which points back up to the containing car. This is automatically filled at the construction of the assemblage. Going forward this may well govern life-cycle and access rules for the Engine.

"Reactive" components

A reactive relationship is established with the dollar sigil

Game = PlayArea Ball $Time
Time = Int/t
...

This declares that Game is a dependent of Time. If the Time value changes, the Game will update itself. Behind the scenes we turn the Time object into an observable and the Game object becomes a subscriber to it.

WCHNT also takes inspiration from functional programming. I'm a big fan of immutability. And most methods in WCHNT are single expressions which cannot mutate the object. In fact there is only one method that can mutate an object in place, the update() method. When one class is subscribed to another, changes to the observed object automatically trigger the update method on the observer object.

"External" components

An external component is simultaneously one which is defined outside the assemblage, and considered to be temporarily lent to it.

Game = PlayArea Ball $Time @GraphicsContext
...

The main effect of declaring a class to be external is that WCHNT assumes it's not defined within the assemblage at all, and comes from the platform or framework or wider context within which the program is run. An object of this class will need to exist prior to the Game's construction and be passed as an argument to the construction. We assume that the object will live on after our assemblage has finished and that it is someone else's responsibility to destroy it.

"Mailbox" classes

It's presumed that WCHNT programs are run in a harness or external framework which manages perhaps the top level game loop or REPL loop.

Mailbox classes are how this external environment passes information in to the assemblage.

>Keys = Bool/left Bool/right Bool/up Bool/down
Game = PlayArea Ball $Time @GraphicsContext Keys 

The > sigil tells us that the values in the Keys object are expected to be mutated by the external context. In fact, the external context should be forbidden from mutating any object which isn't a mailbox object. So in this example, Game can look inside its Keys component to find out the latest user key presses passed into the assemblage from the harness.

Sum types

We borrow one more trick from Haskell's data. The ability to define sum types. Or, in more pedestrian OO terms, interfaces.

Shape = Triangle | Square | Circle

This declares an interface called Shape and the three specific shape classes that implement it.

Enums

Enums are defined in the schema declaration with values in quotes.

Action = "Run" | "Jump" | "Duck" | "Shoot"

The Structure of an Assemblage

A full assemblage is defined in a single file. And is written in a literate programming style.

In 2026 it's clear that markdown has become the universal file-format. And that - yes, we gotta mention it - a world with AI assistance means that much "programming" is going to be in natural language. Project WitchChant is getting ahead of the curve by deciding that Markdown is the official format for a WCHNT assemblage. And that we embed the snippets of formal code in a natural language document.

Another of my bugbears over the years is the lack of navigability in my IDE. "Why isn't everything wiki?" I wailed back in about 2002. Why can't I annotate my code with hyperlinks to important related ideas? Well, the typical WCHNT environment is wiki. With assemblages defined on pages which can be hyperlinked together.

A WCHNT assemblage, therefore, is a file or page of markdown with a number of required sections (which we also call "phases"). Each section or phase starts with a header and contains code in a fenced block. It can also contain other free text for discussion and documentation. And hyperlinks to sibling assemblages.

The phases are :

Import Phase

Assemblages are intended to be small groups of tightly coupled classes. As such we consider that there's no need for data hiding or opacity between them. Classes in the same assemblage are assumed to be able to see into each other and are not trying to hide their structure from each other.

This does not mean that data hiding and abstraction layers are bad things. Another contention of assemblage programming is that different scales require different kinds of membrane. So within an assemblage, "programming in the small", we can assume cooperation and transparency. For programming in the large, the boundary between assemblages is still considered opaque and the usual virtues of abstraction and data hiding should be respected.

You could see an assemblage as equivalent to a "package" or namespace.

The Import phase of the assemblage file is the place where we pull in other assemblages. Right now this is still work in progress and unsupported.

Schema Phase

The Schema phase has been covered fairly comprehensively above.

Construction Phase

The Construction phase gives the entire assemblage its initial values. We use a format based on Clojure's "hiccup" where an object is defined as a list of data in square brackets, with the first element being the name of the class in Clojure's :keyword format (ie. with a colon on the front.) IMHO hiccup is more readable than XML or JSON. And easy to work with in Clojure, the language in which the WCHNT compiler is written.

Here's an example Construction of the initial Game with PlayArea and Ball whose schema we saw earlier.

[:Game [:PlayArea [:Rect 0 0 400 400]] [:Ball 100 100 4 -4 5]]

Note that we usually have to give the class name. But in some cases, where there is no ambiguity, we can choose to exclude it to reduce noise and increase legibility. For example, the above could have been written

[:Game [:PlayArea [0 0 400 400]] [:Ball 100 100 4 -4 5]]

The square brackets of the inner object are still needed though.

Wiring up of reactive dependencies and references from a context-dependent object to its containing parent are done automatically during the construction of the assemblage.

Methods Phase

The Methods phase specifies the behaviour of the assemblage.

Ball::bounceDx = { Rect/playAreaRect |
  if ((x < playAreaRect.x) or (x > (playAreaRect.x + playAreaRect.width))) { -dx } else { dx }
}

Methods are names bound to code blocks. A code block has the syntax { parameters | expression}.

Almost all methods are expressions that return a new value which is either a primitive or an object. To construct a new return object we use the same syntax as the Construction phase of the program. Though we can mix in standard arithmetic, logic etc.

Foo::bar = { Int/x | [:Pair x (x * x)]}

But update is a special method. It's the one method of an object which is considered to mutate it "in place".

Game::update = {
  ndx = ball.bounceDx(playArea.rect).
  ndy = ball.bounceDy(playArea.rect).
  moved = [:Ball (ball.x + ndx) (ball.y + ndy) ndx ndy ((ball.rad+1)% 50)].
  [:Game playArea moved time]
}

Although this looks like it's creating a new Game object, because it's the update method of Game, it compiles into something that mutates the existing Game object.

Methods can be either single expressions or a "let binding" ie. a sequence of further definitions, separated by . and then a final expression.

Foo:baz = {Int/x |
  y = x * x.
  [:Pair x y]
}

The if in WCHNT is a conditional expression. Not a control structure.

udx = (if (keys.right) { 1 } else { 0 }) + (if (keys.left) { -1 } else { 0 }).

Arrays have a map method which takes an anonymous code block / lambda and maps it across all members of the array.

movedObs = pollutants.map({ Pollutant/p | p.move(r) }).

Arrays also have a fold (aka "reduce" in other languages) function. Which lets you reduce the whole collection to a single value.

hit = movedObs.fold(false, { acc, o | if (acc) { true } else { movedP.collides(o) } }).

In that line we are testing each of the pollutants in movedObs to see if the player in movedP collided with it.

Note that the accumulator is the first argument to the fold, and the block the second.

Target Phase

All programs run in some kind of environment. And often it's the interface between your program and that environment that causes the most trouble. The philosophy of WCHNT is to make this environmental dependency more explicit and legible.

The Target phase is the place where we put information about how the environment calls into our code. Right now, when WCHNT is still very embryonic, we have two target environments for it. One is compilation to the Haxe language. The other is an interpreter running in the browser where you can play with WCHNT today.

The Target consists of configuration flags and target specific code to provide the main or game loop. For Haxe compilation target specific code is written in Haxe itself. In the browser it's in Javascript.

Currently we provide a simple graphics API which is the same across the OpenFL and browser canvas targets.

Here, for example, is the Target for an OpenFL ball bouncing program.

%openfl

%init
var assemblage:Game;

function init():Void {
    assemblage = gameFactory();
}

%step
function step():Void {
    assemblage = assemblage.step();
    var r = assemblage.playArea.rect;
    var b = assemblage.ball;
    wchntGraphics.clear();
    wchntGraphics.beginFill(0x2a2a2a);
    wchntGraphics.drawRect(r.x, r.y, r.width, r.height);
    wchntGraphics.endFill();
    wchntGraphics.beginFill(0xf2f2f2);
    wchntGraphics.drawCircle(b.x, b.y, b.rad);
    wchntGraphics.endFill();
}

You can see we start with %openfl to tell the compiler which environment we're targeting. Then we define an initialisation phase in platform native code, in this case Haxe. That declares a variable to hold the assemblage, and a standard init() function which calls the gameFactory() function. Because Game is the top level class in our assemblage, the compiler has emitted a factory function with name gameFactory() to build the assemblage as defined in the Construction phase.

The step() function is the payload for a default "game loop" that the OpenFL harness runs. It's called every tick of the clock and we can see that the first thing it does in this example is call the Game's own step() function.

You'll notice that this particular program doesn't mutate the assemblage. Game's step() function just creates a new Game object at each step. Which is clean but inefficient. To mutate the Game in position we'd have to put the mutating code in Game's update() method and call assemblage.update(); in this target code.

The rest of this function is about actually drawing the state of the game with the wchntGraphics that object comes from the harness. In this example we do the actual drawing in Haxe, leaving the WCHNT code decoupled from the actual rendering.

But it is also possible to define draw() functions in WCHNT and pass the wchntGraphics object in as an external component.

Ball::draw = {@WCHNTGraphics/g | g.beginFill(0xf2f2f2).drawCircle(x,y,rad).endFill()}

Then in the Haxe

b.draw(wchntGraphics).

The choice is what makes sense in the particular application and target environment.

The target will need to provide a WCHNTGraphics class with those beginFill, drawCircle, and endFill methods.

And note that WCHNT doesn't have imperative programming, so a sequence of graphics calls like this has to be written chained together in "fluent" style.

WCHNT doesn't hide or resolve all your environment or platform integration problems. IMHO it's always hard to interface between the purity of data manipulation inside the program and the messy outside world. But the hope is that by making the choices more visible. And placing them directly in the assemblage definition rather than relegated to obscure external config files, we actually make dealing with it more straightforward.

The structure of an assemblage file still enforces a clean separation in the program between the different layers of your system : the inner data structure, the initialisation from data, the behaviour, and finally this platform integration. (I sometimes borrow the term "shearing layers" from Stewart Brand to talk about these different layers.)

The hunch behind WCHNT and assemblage programming is that, in the small, this separation, and this organisation is easier to read and reason about and manage, easier to navigate, and just generally more comfortable to work with, than the highly splintered way that OO languages normally organise their code.


Learn and Play

Want to know more?

There's a Tutorial, and Guide for the full language.

You can try writing real code in WCHNT in the online environment.

For a full example, see the Pollution Game. That page is a straight HTML rendering of the raw pollution.wcn file in our online environment.