Spatial languages: Writing code in 2D
Spatial Languages: Coding in Two Dimensions
By Nishant Shukla | Published: 2026-07-22
Why do we insist that expressions must be read strictly from left to right on a single line? Linearity is the only way to code.
For decades, we have confined our logic to 1D space. But what happens when we unlock the -axis? To make this a reality, we simply need a parser and an IDE that can handle spatial coordinates.
The Problem with 3-Arity Functions
In most modern languages, we rarely encounter true 3-arity functions written in infix notation. Take Python, for example:
x and y and z
While this looks like a single operation, it is actually just a composition of multiple binary functions. Even Python's shorthand for (a and b) and (b and c) a and b and c follows this same binary logic.
To illustrate the need for a spatial approach, let's look at a custom operator I've been developing called andFlip.
Defining andFlip
The logic is simple: it takes two inputs and flips the state of a target if both inputs are true.
def andFlip(args):
# args[0] and args[1] are inputs, args[2] is the target
if args[0] and args[1]:
args[2] = not args[2]
return args[2]
Two Ways to Implement This
Method 1: The Conventional Approach
We could use currying. The infix operator @@ takes two arguments and returns a function that waits for the third:
(a1 @@ a2) a3
It works, but it's boring.
Method 2: The Spatial Approach What if we used the vertical axis? I envision a notation where the target sits above or below the operator:
This makes chaining significantly more intuitive. Consider this flow:
In this chain, x @@ y toggles , and then toggles . This creates a successful three-way toggle where only flips if are all true.
Real-World Application: The Chicken Coop
I applied this logic to the automatic door of my chicken coop. The system uses a microcontroller to monitor three specific conditions:
| Variable | Condition | Description |
|---|---|---|
x | Photometer | Has the dusk/dawn threshold been crossed? |
y | Stability | Has the reading remained steady for 5 minutes? |
z | Recency | Has the gate avoided toggling too recently? |
The Logic: The door should only toggle when all three conditions align. If a cloud passes by,
xmight trigger, but it won't satisfyy, so the door remains still.
The controller uses the chained expression:
Why not just use x and y and z?
Because a standard and mirrors the sensors; the door would swing back the moment a condition failed. The @@ operator toggles in place, meaning the door moves once and stays put, leaving the intermediate result in (a "scratch bit").
Vertical Chaining and Logic Puzzles
Because is mutable, this isn't a "pure function." This leads us to some interesting spatial puzzles.
-
Puzzle 1: Resetting to 0 How do we reset to 0 within the same expression to make it appear immutable? Since repeating the toggle resets the value, we can wrap the expression:
Data flows top-to-bottom and left-to-right. If and are true, the first
x @@ yflips , and the second one flips it back. This is a spatial representation of the CCCX gate in quantum computing. -
Puzzle 2: The "Dirty" Ancilla What if starts with an unknown value ?
The previous trick fails here. If and all inputs are true, stays 0 (incorrect). The solution is a mirrored 2D structure:
This resets to while correctly calculating . This mimics resetting a borrowed ancilla in quantum circuits.
Comparison with Qiskit
In Qiskit, this same logic looks like this:
from qiskit import QuantumCircuit, QuantumRegister
x = QuantumRegister(1, 'x')
y = QuantumRegister(1, 'y')
z = QuantumRegister(1, 'z')
t1 = QuantumRegister(1, 't1') # 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)
The Qiskit code is harder to parse because of the constant variable tracking. A circuit diagram is better, but not always scalable.
Defining New Spatial Operators
We can define new operators using the := symbol, where the left side is a spatial shape and the right side is its rewrite rule.
Let's define a single @ operator: x @ y flips if is true. Now we can build the maj (majority) operator:
b maj c
(a @ b) @@ (a @ c)
a := a
This operation flips and if is true, then flips if both resulting sides are true. Mathematically, this computes:
This is the MAJ gate used in quantum ripple-carry adders to determine the next carry bit. Note that maj leaves and "dirty" (holding and ), similar to our borrowed ancilla problem.