Spatial languages: writing code in 2D
I guess expressions don’t really need to read left-to-right on a<br>line.
Why can’t a be written vertically too?
All this time we’ve been writing expressions in 1D space, but what<br>happens when we unlock an extra dimension?
Of course the IDE and parser need to play nicely for all this to<br>work. Even then, what’s the point? Well, let’s take a look at these<br>examples below.
3-arity functions
We don’t really see 3-arity functions written in infix notation<br>much.
For example, in Python (and most other languages), you can<br>and 3+ things at once:
x and y and z
Technically, that’s just composing multiple binary functions<br>together.
Even this shortcut in Python to simplify<br>(a is more of the same thing:
a b c
Now, let me introduce you to a funny little operator I’ve been<br>playing with called andFlip, which motivates the need for a<br>true 3-arity infix notation. First, here’s the definition of the<br>function:
def andFlip(args):<br>if args[0] and args[1]:<br>args[2] = not args[2]<br>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<br>try anyways!
Define @@ to be the infix symbol for the<br>andFlip operator. What does it look like in practice?
Method 1: the standard way
One option is to curry andFlip: the infix<br>@@ takes 2 args and returns a function awaiting the 3rd,<br>like so:
(a1 @@ a2) a3
Yeah, that’s alright. It works. But…
Method 2: the goofy way
Forget what you know about code for a second. If I could flick a<br>magic wand, I’d wish for the 3-arity notation to look like this:
a3<br>a1 @@ a2<br>Might as well make use of the y-axis, right?
It makes chaining so much easier:
t1 = 0<br>t2 = 0
t1 t2<br>(x @@ y) @@ z<br>In the chain, x @@ y toggles t1, and<br>t1 continues the chain t1 @@ z, which toggles<br>t2.
That’s a successful three-way toggle, where all x,<br>y, and z must be true for the target to<br>toggle.
Btw, since we’re working in 2D space now, the following<br>expression,
t1<br>x @@ y<br>is equivalent to:
x @@ y<br>t1<br>Example: my chicken coop door
Consider the automatic door I’ve installed on my chicken coop.
The coop’s automatic door (right) and its<br>controller box
With just a photometer and its internal clock, the microcontroller<br>tracks:
x – the photometer crossed its dusk/dawn threshold
y – the reading has held steady for five minutes
z – the gate has not recently toggled
The door toggles only when all three line up. A passing cloud trips<br>x but never outlasts y, so the door stays<br>put.
The coop’s controller is the same chained expression from above:
t1 = 0<br>t2 = 0
t1 t2<br>(x @@ y) @@ z<br>You might be wondering, why not just write<br>x and y and z? Because then the door would mirror the<br>sensors, swinging right back the moment a condition fades.<br>@@ toggles in place: the door moves once and stays, and the<br>partial answer lands in t1, a scratch bit.
Spatial languages get even more fascinating when we introduce<br>vertical chaining!
Vertical chaining
That mutable variable t1 disqualifies the expression<br>from being called a “pure function”. But can we fake it? Can we reset<br>t1 back, all within the same expression?
I’d like to introduce vertical chaining through two puzzles.
Puzzle 1: reset t1<br>back to 0
As mentioned, in the chaining examples above, the variable<br>t1 is mutable.
Here’s a puzzle: how can we reset it back to 0, so the<br>expression “appears” immutable?
You may be tempted to just append the statement t1 = 0<br>and call it a day, but the goal of the puzzle is to do it all within the<br>same expression.
Give it a shot before you scroll down!
Here’s the answer. Since t1 can be reset by repeating<br>the same thing that was used to toggle it, we can combine expressions<br>into a larger 2D one, like so:
t1 = 0<br>t2 = 0
(x @@ y) @@ z<br>t1 t2<br>(x @@ y)<br>Ok, I’m probably blowing your mind right now.
The data flow is still as you’d expect in a programming language,<br>left-to-right and top-to-bottom. If x and y<br>are true, the first x @@ y flips t1 and then<br>the second x @@ y resets it. Otherwise, both leave<br>t1 untouched.
This puzzle actually models the CCCX gate in quantum<br>computing, where uncomputing the<br>intermediate t1 value within the same circuit is an<br>important requirement.
Puzzle<br>2: reset the temporary variable back to the original value
If t1 starts off with an unknown boolean value<br>n, then it gets more complicated.
t1 = n<br>t2 = 0
t1 t2<br>(x @@ y) @@ z<br>The same trick above to reset t1 back to its original<br>value won’t work.
See for yourself: when t1 = 1 and x,<br>y, and z are all true, then t2<br>remains 0, which is incorrect.
t1 = 1<br>t2 = 0
(1 @@ 1) @@ 1<br>t1 t2<br>(1 @@ 1)<br>So, what’s the answer?
Tada!
t1 = n<br>t2 = 0
t1 t2<br>(x @@ y) @@ z<br>t1 t2<br>(x @@ y) @@ z<br>Absolutely legendary. This single 2D expression resets<br>t1 back to n while producing the correct<br>result for t2.
This puzzle is inspired by the concept of resetting a borrowed ancilla in<br>quantum computing. t1 is the “borrowed” or “dirty” value<br>that remains unchanged.
Here’s the...