Text is a serialization of an n-dimensional work space. "N-dimensional" doesn't have a formal definition I'm aware of but the major characteristic of it is that a new dimension can pop up anywhere. If you feel like you want to double-click on that, you can see my earlier comment here: https://news.ycombinator.com/item?id=35096647
A conventional textual language can represent everything mentioned there, whereas 2D layouts struggle almost immediately to represent things that textual layouts represent quite easily.
It can still be a useful "see the world differently" exercise, but it's the opposite of "freeing your mind"... it's stepping into a fairly small subset of functionality, albeit an unconventional one, and seeing what you can still manage to do even so. Rather than "unlocking a new dimension" it's actually throwing away n-2 of them. The experience of using this for any period of time would reveal this, as it would be a constant struggle to represent even very simple algorithms in this format.
I post this mostly because of the "familiarity breeds contempt" effect that programmers seem to get hit with for text. There is a reason we use text and it's not lack of imagination or being stuck in our ways... it is actually an incredibly rich and powerful format that poses a serious challenge for any alternative to overcome because of the astonishing combination of compactness combined with representational power.
Notably the mental model of the hardware is different for each case. Other species of languages like Lisps and Golang and SIMD fascinate me because computer hardware fascinates me.
I like the toy 2D textual experiment the author has given us, and I agree with you it would not scale. Since the author calls their toy 'goofy' they probably agree. But I would contest (edit: your point that we are not stuck in our ways.) We are by and large seriously stuck in our ways. Even if a perfect visual/spatial programming language dropped into our laps today, I posit it would take over 50 years to meaningfully supplant C et al..
Nitpick - the andFlip function is wasting a whole dimension with unnecessary lines, and also modifying its input argument unnecessarily (yuck!), and it takes 3 arguments as an array for no good reason.
Better: andFlip = lambda a,b,c : not c if (a and b) else c
Except this is really just a normal 2 argument ternary, it does nothing interesting with a or b individually, it treats them as a unit, so Iβd argue this shouldnβt even be a function.
What I was looking for was actually "Piet":
I had difficulty reading large blocks of text when younger partly dyslexia I found out about much later, but mostly just overwhelming to look at. Then I learnt how to think of code as checkpoints, paths and basically a 2d map in a sense.
It helped me get used to huge walls of text. Maybe since we now have AI we can do some fun stuff in this direction since human ergonomics for writing code matter less than making code fun to read so that we can read and validate massive AI code.
Would be a fun experiment now that I think about it, I guess I found what I am doing tomorrow.
Would a better example be Python's inline if else construction?
x = 3 if thing else 4I just started note taking by hand and I find it way more enjoyable than typing. So maybe itβs a medium to force myself to code like in the old days.
I was looking for something like spatial computing and all I could find were esolangs, UML, and maybe punchcards? This gives me an idea on how this could be useful.
It is used to compose songs. You can see the demo in the linked page.
The great gift of language is that it allows us to talk about arbitrarily complex things using a stream-like medium. Human speech is fundamentally 1D. The tree like structure of the concepts in language then facilitates the exponential growth in complexity.
Or maybe centuries of human progress just went completely in the wrong direction? Unlikely.
Nothing wrong with being silly, of course.
Or, you stick to oldschool old people. Keep things simple.
Text works and is hard because it's a projection of a mental representation. The whole reason we can work with N-dimensional structures in text is because the dimensionality lives in the programmer's head. There isn't really any other way to represent say a >3 dimensional array other than laboriously flattening it into text and expecting the next guy to rotate the hypercube in his mind. That's neither a good nor bad thing, it's purely a fact of our 3+1 dimensional universe.
A shocking number of problems can be best represented in higher dimensions and there's simply no way to encode that information losslessly. Being simple 3D creatures (I'd argue 2D in fact) the only way to do that is by projecting it down into a lower-dimensional space.
As a 2D representation, I've been using flow based programming (FBP)[1] and it's incarnation in Node-RED[2]. The trick here is to understand how to use FBP to create more generalistic programs, i.e., to prove it's Turing Complete.
I think it would be a mistake to just start out with a 2D approach without also taking all the learnings we have made in 1D, i.e. TDD, OOP, KISS, DRY, Functional, Message Passing, Processes etc, and porting those to a 2D representation.
[1]: https://jpaulm.github.io/fbp/
[2]: https://nodered.org/
I've got my 3D glyph rendering system at glyph3d.dev, and use cases like this help accelerate layout and relationship development immensely. I greatly appreciate your sharing this!
Would you happen to have a trove of similar research or papers I could sponge off of you? Or maybe even a few minutes of your time in some chats / emails to discuss how these tools can be brought to a modern runtime?
Visual programming, another ever-recurring supposed replacement for text that is just obviously so much better and programmers are just stupid for sticking to plain and boring text, has a similar problem, where in return for whatever visual organization it makes possible (but does not force, since you can still make stupid visual programs) it is countered by the annoyance of suddenly having so many more constraints on the program, like, having to worry about layout, and how the lines cross each other, and whether some part of the program is literally overlapping another, and how all these concerns tend to grow as O(n^2) on the number of elements if not confined somehow. This one has led to some modest success in certain limited environments, but as a replacement for textual programming it fundamentally flounders on the fact that its additional constraints creates problems significantly faster than it can solve problems.
Anyone that says text is 1D has a poor model of code in his or her mind.
I think this is because I reason about programs being in some state ~= being positioned at a certain function with some context I carry around. I'm sure it's not the only way to think about it, but it's a reasonable abstraction over what computers do. It's how we oriented our debuggers and early IDEs on as well, and carried this over to modern tools.
Definitely something I'll be thinking about during the next few days.
It's surprising that there is any art at all if 1D textual representation is the ultimate way of representing concepts of the world around us. Incredible really that folks waste their time define colours for such thing as the sunshine.
Oh sorry, of course, you were talking of code - meaning that algorithm concepts are best represented in text? Again, I doubt that very much. Laziness of humans is probably the bigger reason why we're stuck with an inefficient textual representation of higher dimensional concepts.
Of course, if you're sure that the entire rest of the industry is just lazy, then by all means feel free to join the set of people who have spent vast amounts of time trying to prove out these obviously better ideas. You've got AI now, I'm sure that will make it easier to prove it out. I imagine it can bang together a visual programming language nowadays in a few prompts whereas earlier generations actually had to rather laboriously put one together. You can get right to the part where you actually get to play with the ideas and try to make it fix programming in probably 1% of the time as the efforts that have periodically made HN's front page.
My standard challenge[1] is to make a visual quicksort algorithm that people will generally agree is nicer to read than a textual representation, where the textual representation may include comments (though so can the visual replacement). It's chosen to still not be fundamentally impossible, but to get past the sorts of "look, I can interate on a list and increment each value" demos they tend to come with.
[1 2 3; 4 5 6; 7 8 9]
Instead of |1 2 3|
|4 5 6|
|7 8 9|
The concept of vertical space to separate blocks of text is important. The concept of physically moving the next chapter to a separate sheets of paper in books is equally important.In code we split the code into files. Then within the files we arrange the code with lines and indentation. That's all for humans. The computer eagerly throws those away.
ADDENDUM
> meaning that algorithm concepts are best represented in text? Again, I doubt that very much.
One of the (most?) important things that algorithms encode is time. Because an algorithm is a description of a process. The only good representation would have been to snapshot the evolution of state.
But that's wasteful. So instead we have statements and expressions that we assume are atomic. Then we have identifiers for referencing and finally we have procedures and functions to cluster those statements and the associated call/return pattern or the single jump/goto to economize on writing.
So code is at least 2D as operations are described in a line and the lines are arranged by time. But the lines are not linear, instead they are grouped into labelled units and the proper evolution are functions call and jump instructions.
\ | /
.-'-.
-- ( ) --
`-.-'
/ | \
That's a sun.Much like HN is full of textual description and links to images - this entire forum demonstrates the limitations of a textual visual representation.
Yes it works but it's far more verbose than an image would be. Hence an image is worth a 1000 words. And hence HN is more verbose than it needs to be. So is coding more verbose than it needs to be. That's why DSLs (Domain Specific Language) get invented: to reduce the verbosity.
And so it is with a 2D representation: it reduces the verbosity required to transport ideas and concepts. UML is another invention to reduce the verbosity and increase the understanding of the representation of complex systems. Yes UML can be represented as text (see Mermaid[1]) but the UML image is probably a lot more understandable than the Mermaid representation.
((1 2 3)(4 5 6)(6 7 8))
Of course you ignore that representing a tensor of rank higher than 2 is just as difficult in 2d as a matrix is in 1d.The word "sun" is less verbose than your drawing, but more clear(I would not have recognized it as a sun) and has all the information implied.
So yes in certain context, a diagram may be more useful to get a point across. But once that shared understanding has taken place, Text are more economical.
A lot of foundational aspects of computing are too abstract in nature to be able to do a good 2D or 3D representation. So what we do is transforming them into streams of symbols and do symbolic manipulations on them. And with symbolic manipulation, you get abstraction.
The main issue is that a lot of people are not good at symbolic representation and manipulation. Because it's abstract and it's entirely a product of the mind.
The power of computers is that they can do the tedious part of symbolic manipulations, repeatedly and without failing. And with peripherals interfacing, they can translate symbols from/to reality.
Exactly what I am implying: using the computer to provide a better abstraction is what is needed.
I agree that text has a certain universality however as you also say, a lot folks don't work with symbols, they work with images (for example) or colours (synesthesia) or shapes perhaps.
Besides move up in dimensions is a continuation of a natural progression in computing: cogs, switchs, punch cards, keyboards, mouse, touch .... VR, AR etc. Currently we're (coders) are somewhere between keyboard and mouse while others (most folks that use smart phones) are at touch. Smart phones users are also programmers who code their device using largely visual concepts (at least I've not seen anyone set their alarm using a terminal on a smart phone!).
Now "Oh but it's not possible to represent those concepts that we work with visually, only text will do" argument doesn't really hold water since it just demonstrates a lack of imagination to find a way to use other representations for those concepts that are essential to programming. Strangely that was possible when moving from switches to keyboards, why shouldn't it be possible even further.
Recently I had the thought that in fact, textual programming (as a paradigm) is now the sole domain of AIs. I've heard so many coders say they have not coded a line of code in months/years, AI is doing all the coding for them. Hence, I think, we humans (those of us here who are humans) should step up to the next domain for coding: 2D and then 3D and explore improved abstractions for the concepts we try to represent in a textual manner (even these concepts are abstractions of the real world).
I did not say that. I said that we do not have good representations of those concepts.
The thing is that programming is rarely the end goal of things. It's merely the help. There's often an actual task or process that existed before we got to the programming itself. The goal is to automate some subset or all of it using computers. And programming is about constructing a model of the task/process, encode the state and the rules that govern the evolution of the state, and then interfacing with the real world to get data (sensors), manipulate them according, and then produce actions (with actuators).
Text is universal because it's symbolic (and with it come abstraction which is recursive in that form). And we managed to get a good representation of time (which is also a key part of the task/process) with vertical alignment. And we can implement extra dimensions that matters to comprehension with modularization and whats not. The cons are that symbol manipulation can be abstract and requires a good deal of training to transfer that knowledge.
The issue with most 2D representation is that they're not flexible enough. Symbols are recursive. Meaning to encode more things, you just needs to repeat symbols. And then you can use symbols to encode group of symbols. Like we have 1 and 0 as the base of everything, then we encoded the alphabet using ASCII, then we encoded programming concepts using tokens (group of letters and other special symbols) and grammar, then we goes one to define higher other of things like variable definition, functions, types annotation, which just more syntax and grammar rules.
> Besides move up in dimensions is a continuation of a natural progression in computing: cogs, switchs, punch cards, keyboards, mouse, touch .... VR, AR
That's an evolution in interfacing. And the one thing about interfacing is that it has to adapt to the physical reality of human interactions. Which is often not great for the actual reality of the tasks/process that needs to happen. In the physical world, it's often physical laws and causal order that does all the work, we are only the perturbation force. We move the steering wheel and the car turns, but that's mostly due to mechanical laws than the actual act of turning the wheel.
With programming, the task is often to recreate all the laws that govern the evolution of a process and then offers an interface for controlling that evolution. 2D and 3D diagrams are very bad at describing ALL the relations between the different components of a system. Let's take the "F = ma" equation: How would you represent the force, mass, and acceleration concepts in order to create like a very simple physic engine? We can create a special interface for that, but that interface would be likely bad for creating a bookkeeping software or a text editors.
Symbolic representation is universal because there's only two things that matters, the symbols and the rules of notation. And the genius of the Turing Machine (and equivalent) is that at the foundation of things, you do not need a lot of symbols and a lot of rules. You can go from one system to the next by creating a translation mechanism. But if you leave the symbolic world, you lose universality for power. That's happen with GUI software and other 2D and 3D representation of things. Which is why you see that professional software have a scripting layer or a plugin mechanism.
I guess expressions donβt really need to read left-to-right on a line.
Why canβt a < b be written vertically too?
a
<
b
All this time weβve been writing expressions in 1D space, but what happens when we unlock an extra dimension?
Of course the IDE and parser need to play nicely for all this to work. Even then, whatβs the point? Well, letβs take a look at these examples below.
We donβt really see 3-arity functions written in infix notation much.
For example, in Python (and most other languages), you can and 3+ things at once:
x and y and z
Technically, thatβs just composing multiple binary functions together.
Even this shortcut in Python to simplify (a < b) and (b < c) is more of the same thing:
a < b < c
Now, let me introduce you to a funny little operator Iβve been playing with called andFlip, which motivates the need for a true 3-arity infix notation. First, hereβs the definition of the function:
def andFlip(args):
if args[0] and args[1]:
args[2] = not args[2]
return args[2]
It takes 2 inputs and flips the target if both inputs are true.
Thereβs just no good way to write this with infix notation. But letβs try anyways!
Define @@ to be the infix symbol for the andFlip operator. What does it look like in practice?
One option is to curry andFlip: the infix @@ takes 2 args and returns a function awaiting the 3rd, like so:
(a1 @@ a2) a3
Yeah, thatβs alright. It works. Butβ¦
Forget what you know about code for a second. If I could flick a magic wand, Iβd wish for the 3-arity notation to look like this:
a3
a1 @@ a2
Might as well make use of the y-axis, right?
It makes chaining so much easier:
t1 = 0
t2 = 0
t1 t2
(x @@ y) @@ z
In the chain, x @@ y toggles t1, and t1 continues the chain t1 @@ z, which toggles t2.
Thatβs a successful three-way toggle, where all x, y, and z must be true for the target to toggle.
Btw, since weβre working in 2D space now, the following expression,
t1
x @@ y
is equivalent to:
x @@ y
t1
Consider the automatic door Iβve installed on my chicken coop.

With just a photometer and its internal clock, the microcontroller tracks:
x β the photometer crossed its dusk/dawn thresholdy β the reading has held steady for five minutesz β the gate has not recently toggledThe door toggles only when all three line up. A passing cloud trips x but never outlasts y, so the door stays put.
The coopβs controller is the same chained expression from above:
t1 = 0
t2 = 0
t1 t2
(x @@ y) @@ z
You might be wondering, why not just write x and y and z? Because then the door would mirror the sensors, swinging right back the moment a condition fades. @@ toggles in place: the door moves once and stays, and the partial answer lands in t1, a scratch bit.
Spatial languages get even more fascinating when we introduce vertical chaining!
That mutable variable t1 disqualifies the expression from being called a βpure functionβ. But can we fake it? Can we reset t1 back, all within the same expression?
Iβd like to introduce vertical chaining through two puzzles.
t1 back to 0As mentioned, in the chaining examples above, the variable t1 is mutable.
Hereβs a puzzle: how can we reset it back to 0, so the expression βappearsβ immutable?
You may be tempted to just append the statement t1 = 0 and call it a day, but the goal of the puzzle is to do it all within the same expression.
Give it a shot before you scroll down!
Hereβs the answer. Since t1 can be reset by repeating the same thing that was used to toggle it, we can combine expressions into a larger 2D one, like so:
t1 = 0
t2 = 0
(x @@ y) @@ z
t1 t2
(x @@ y)
Ok, Iβm probably blowing your mind right now.
The data flow is still as youβd expect in a programming language, left-to-right and top-to-bottom. If x and y are true, the first x @@ y flips t1 and then the second x @@ y resets it. Otherwise, both leave t1 untouched.
This puzzle actually models the CCCX gate in quantum computing, where uncomputing the intermediate t1 value within the same circuit is an important requirement.
If t1 starts off with an unknown boolean value n, then it gets more complicated.
t1 = n
t2 = 0
t1 t2
(x @@ y) @@ z
The same trick above to reset t1 back to its original value wonβt work.
See for yourself: when t1 = 1 and x, y, and z are all true, then t2 remains 0, which is incorrect.
t1 = 1
t2 = 0
(1 @@ 1) @@ 1
t1 t2
(1 @@ 1)
So, whatβs the answer?
Tada!
t1 = n
t2 = 0
t1 t2
(x @@ y) @@ z
t1 t2
(x @@ y) @@ z
Absolutely legendary. This single 2D expression resets t1 back to n while producing the correct result for t2.
This puzzle is inspired by the concept of resetting a borrowed ancilla in quantum computing. t1 is the βborrowedβ or βdirtyβ value that remains unchanged.
Hereβs the same circuit in Qiskit:
from qiskit import QuantumCircuit, QuantumRegister
x = QuantumRegister(1, "x")
y = QuantumRegister(1, "y")
z = QuantumRegister(1, "z")
t1 = QuantumRegister(1, "t1") # starts in unknown state n
t2 = QuantumRegister(1, "t2") # target, starts at 0
qc = QuantumCircuit(x, y, z, t1, t2)
qc.ccx(x, y, t1)
qc.ccx(t1, z, t2)
qc.ccx(x, y, t1)
qc.ccx(t1, z, t2)
Iβd argue that the Qiskit implementation is harder to follow, because of the back-and-forth variable tracking.
By the way, hereβs the corresponding quantum circuit diagram:
wires: x, y, z, t1 = n, t2 = 0
outputs: x, y, z, n, xΒ·yΒ·z
ccx x y t1
ccx t1 z t2
ccx x y t1
ccx t1 z t2
In some ways, this circuit diagram is easier to follow, but probably not scalable.
The spatial language syntax is the best of both worlds.
First, meet @@βs little sibling. A single @ takes one condition instead of two: x @ y flips y whenever x is true.
With @ and @@ in hand, we can define new operators, spatially. The left side of := is a shape, and the right side is what that shape rewrites to:
b maj c (a @ b) @@ (a @ c)
a := a
This strange operator flips both b and c when a is true, and then flips a if both sides came out true. That computes a ^ ((a^b) & (a^c)) into a, which happens to equal the majority of the three inputs, which is why this operator is called maj.
And maj is no arbitrary choice. Itβs the MAJ gate from the classic quantum ripple-carry adder, where the majority of (carry, bit, bit) is the next carry.
You may have noticed that maj leaves b and c dirty, holding a^b and a^c, the same kind of mess as our borrowed t1. To clean up, weβll also introduce uma, the partner operator. The name comes from the same adder paper, βUnMajority and Addβ: it un-majorities c, restores a, and adds the sum onto b:
c uma b ((b @@ a) @ a) @ b
a := c
Letβs see what happens when we wire them up.
We can build a 3-bit adder out of three majs that ripple the carry up through the a wires, with the final @ dropping the carry-out on c3, and finally one line of umas resetting all the scratch wires:
c0 = 0
c3 = 0
(b2 maj (b1 maj (b0 maj c0))) @ c3
a2 a1 a0
(( . uma b2) uma b1) uma b0
c0
Thatβs a full 3-bit adder in a single 2D expression that also cleans up after itself. The b wires now hold a + b, c3 holds the carry-out, and c0 and the a wires come back exactly as we found them. (Set c0 = 1 and you get a + b + 1 for free.)
Notice the two lines mirror each other. The top nests rightward down to b0 and ripples the carry back up; the bottom nests leftward down to b2 and unwinds back out. The shared a1 and a0 labels are the pivots, each serving the maj above it and the uma below it, and the . is a vertical pipe, standing for whatever wire sits directly above it, here a2. The carry chain is stitched together vertically: the uncompute touches the compute, just like in the puzzles.
Hereβs a quick tour of a few more spatial languages.
The grandparent of 2D languages (Befunge, 1993): the instruction pointer physically travels the grid, and >v<^ steer it. Hello world bounces off the walls:
> v
v"Hello World!"<
>:v
^,_@
Orca is a livecoding sequencer where every letter is an operator that reads its neighbors: A adds the value to its left and the value to its right, and writes the sum directly below itself. Sound familiar?
1A2
.3.
A real, shipping language extension: Racketβs #2dcond is a cond evaluated in two dimensions. The left column tests b, the top row tests a, and the cell where both are true runs.
#lang 2d racket
(require 2d/cond)
(define (same? a b)
#2dcond
βββββββββββββββ¦ββββββββββββββββββββββββ¦ββββββββββββββ
β β (pair? a) β (number? a) β
β ββββββββββββββ¬ββββββββββββββββββββββββ¬ββββββββββββββ£
β (pair? b) β (and (same? (car a) β #f β
β β (car b)) β β
β β (same? (cdr a) β β
β β (cdr b))) β β
β ββββββββββββββ¬ββββββββββββββββββββββββ¬ββββββββββββββ£
β (number? b) β #f β (= a b) β
βββββββββββββββ©ββββββββββββββββββββββββ©ββββββββββββββ)
Hexagony runs on a pointy-topped hexagonal grid, like a certain background you may have noticed. Six instruction pointers start at the six corners. Hello world:
H ; e ;
l ; d ; *
; r ; o ; W
l ; ; o ; * 4
3 3 ; @ . >
; 2 3 < \
4 ; * /
Ladder logic is how PLCs run factories (and, done properly, chicken coop doors): each rung is a relay circuit read rail-to-rail, and parallel branches are OR. This classic seal-in rung computes Run = (Start OR Run) AND (NOT Stop):
--+----[ ]--+----[\]----( )
| Start | Stop Run
| |
+----[ ]--+
Run
And an honorable mention for the most successful spatial language of all time: the spreadsheet, where every formula lives at a 2D coordinate and reads its neighbors.
If you know me, you know I love bringing up the Sapir-Whorf hypothesis, which proposes that language shapes the way you think:
Likewise, I believe technology shapes which languages emerge.
Being aware of our biases is a justified mental exercise, at least according to David Foster Wallace, who delivered a commencement speech titled This Is Water. Though his speech was less about ancilla uncomputation in quantum circuits and maybe more about growing up and being compassionate, you get the point. By challenging our habits, we can explore new ideas.