空间语言:二维代码编写
摘要
本文探讨了二维代码编写的概念,通过垂直链式调用和三目中缀运算符简化某些表达式,并以鸡舍门控制器等实际案例为例。
<p><a href="https://lobste.rs/s/gb2xwu/spatial_languages_writing_code_2d">评论</a></p>
查看缓存全文
缓存时间: 2026/07/24 04:59
# 空间语言
来源:https://shukla.io/blog/2026-07/cccx.html
我猜表达式并不一定非要在一行里从左读到右。为什么 `a < b` 不能也竖着写呢?
```
a
<
b
```
这么久以来我们一直在 1D 空间里写表达式,但当我们解锁一个额外维度时会发生什么?当然,IDE 和解析器需要配合好,这一切才能工作。即便那样,意义何在?好吧,让我们看看下面这些例子。
## 3 参函数
我们很少看到 3 参函数用中缀表示法书写。例如,在 Python(以及大多数其他语言)中,你可以一次 `and` 3 个或更多东西:
```python
a and b and c
```
严格来说,那只是把多个二元函数组合在一起。即使是 Python 中简化 `(a < b) and (b < c)` 的这个快捷方式,本质上也是同一回事:
```python
a < b < c
```
现在,让我向你介绍一个我一直在捣鼓的有趣的小运算符,叫做 `andFlip`,它引出了对真正三目中缀表示法的需求。首先,这是函数的定义:
```python
def andFlip(args):
if args[0] and args[1]:
args[2] = not args[2]
return args[2]
```
它接受 2 个输入,如果两个输入都为真,则翻转目标。用中缀表示法写这个简直没有好办法。但无论如何,我们还是试试吧!将 `@@` 定义为 `andFlip` 运算符的中缀符号。实际中它是什么样子?
### 方法 1:标准方式
一种选择是对 `andFlip` 进行柯里化:中缀 `@@` 接受 2 个参数并返回一个等待第 3 个参数的函数,如下所示:
```python
def andFlip_curried(a, b):
def inner(c):
return andFlip([a, b, c])
return inner
# 用法
result = andFlip_curried(x, y)(z)
```
是的,还行。它有效。但是……
### 方法 2:搞怪方式
先忘记你对代码的认知。如果我挥一挥魔法棒,我希望三目表示法看起来像这样:
```
a3
a1 @@ a2
```
不妨利用一下 y 轴,对吧?它让链式操作变得容易得多:
```
t1 = 0
t2 = 0
t1 t2
(x @@ y) @@ z
```
在这个链中,`x @@ y` 切换 `t1`,然后 `t1` 继续链 `t1 @@ z`,这切换了 `t2`。这是一个成功的三路切换,其中 `x`、`y` 和 `z` 必须都为真才能切换目标。
顺便说一句,既然我们现在是在 2D 空间中,以下表达式:
```
t1
x @@ y
```
等价于:
```
x @@ y
t1
```
### 示例:我的鸡舍门
考虑一下我为鸡舍安装的自动门。
鸡舍的自动门(右)及其控制箱
仅凭光度计和内部时钟,微控制器跟踪:
- `x` – 光度计跨越了黄昏/黎明阈值
- `y` – 读数稳定持续了五分钟
- `z` – 门最近没有切换过
只有当这三个条件都满足时,门才会切换。一阵云会触发 `x`,但从未超过 `y`,所以门保持不动。鸡舍的控制器的逻辑就是上面那个链式表达式:
```
t1 = 0
t2 = 0
t1 t2
(x @@ y) @@ z
```
你可能会想,为什么直接不写 `x and y and z`?因为那样门就会镜像传感器,条件一消失就会立刻弹回。`@@` 则是原地切换:门移动一次并保持,而部分答案落在 `t1` 这个暂存位中。
当我们引入垂直链式操作时,空间语言变得更有趣了!
## 垂直链式操作
那个可变变量 `t1` 使得这个表达式不能被称作“纯函数”。但我们可以假装一下吗?我们能否在同一表达式中将 `t1` 重置回去?我想通过两个谜题来介绍垂直链式操作。
### 谜题 1:将 `t1` 重置回 0
如上所述,在上面链式操作的例子中,变量 `t1` 是可变的。谜题来了:我们如何将它重置回 `0`,使得表达式显得“不可变”?你可能想直接追加语句 `t1 = 0` 然后就完事了,但谜题的目标是在同一个表达式内完成所有操作。在向下滚动之前试试看!
下面是答案。既然 `t1` 可以通过重复用于切换它的相同操作来重置,我们可以将表达式组合成一个更大的 2D 表达式,像这样:
```
t1 = 0
t2 = 0
(x @@ y) @@ z
t1 t2
(x @@ y)
```
好吧,我可能在让你目瞪口呆。数据流仍然和你在编程语言中期望的一样,从左到右,从上到下。如果 `x` 和 `y` 为真,第一个 `x @@ y` 翻转 `t1`,然后第二个 `x @@ y` 将其重置。否则,两者都不动 `t1`。这个谜题实际上模拟了量子计算中的 `CCCX` 门,其中在同一电路内对中间值 `t1` 进行**反计算**(https://en.wikipedia.org/wiki/Uncomputation)是一个重要的要求。
### 谜题 2:将临时变量重置为原始值
如果 `t1` 起始于一个未知的布尔值 `n`,那么情况就更复杂了。
```
t1 = n
t2 = 0
t1 t2
(x @@ y) @@ z
```
上面用于重置 `t1` 的同一技巧无法奏效。你自己看看:当 `t1 = 1` 且 `x`、`y`、`z` 都为真时,`t2` 保持为 `0`,这是不正确的。
```
t1 = 1
t2 = 0
(1 @@ 1) @@ 1
t1 t2
(1 @@ 1)
```
那么答案是什么?当当当!
```
t1 = n
t2 = 0
(x @@ y) @@ z
t1 t2
(x @@ y) @@ z
```
绝对传奇!这个单一的 2D 表达式将 `t1` 重置回 `n`,同时为 `t2` 产生了正确的结果。这个谜题的灵感来自量子计算中重置**借用的辅助比特**(https://arxiv.org/abs/quant-ph/9503016)的概念。`t1` 是“借用”或“脏”的值,保持不变。以下是 Qiskit 中的相同电路:
```python
from qiskit import QuantumCircuit, QuantumRegister
x = QuantumRegister(1, "x")
y = QuantumRegister(1, "y")
z = QuantumRegister(1, "z")
t1 = QuantumRegister(1, "t1") # 起始状态未知,n
t2 = QuantumRegister(1, "t2") # 目标,起始为 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)
```
我会说 Qiskit 的实现更难理解,因为需要来回跟踪变量。顺便说一句,以下是相应的量子电路图:
```
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
```
在某种程度上,这个电路图更容易理解,但可能不太可扩展。空间语言语法则是两全其美。
## 定义新运算符
首先,见见 `@@` 的小兄弟。单个 `@` 接受一个条件而不是两个:`x @ y` 在 `x` 为真时翻转 `y`。有了 `@` 和 `@@`,我们可以空间地定义新运算符。`:=` 的左边是一个形状,右边是该形状重写成的样子:
```
b maj c
(a @ b) @@ (a @ c)
a := a
```
这个奇怪的运算符在 `a` 为真时翻转 `b` 和 `c`,然后如果两边都为真则翻转 `a`。这计算了 `a ^ ((a^b) & (a^c))` 为 `a`,恰好等于三个输入中的多数,因此这个运算符被称为 `maj`。而 `maj` 并非随意选择。它是来自经典量子纹波进位加法器(https://arxiv.org/abs/quant-ph/0410184)的 MAJ 门,其中(进位,位,位)的多数就是下一个进位。
你可能已经注意到 `maj` 弄脏了 `b` 和 `c`,使它们持有 `a^b` 和 `a^c`,和我们借用的 `t1` 一样一团糟。为了清理,我们还将引入伙伴运算符 `uma`。这个名字来自同一篇加法器论文,“UnMajority and Add”:它对 `c` 进行反多数化,恢复 `a`,并将和加到 `b` 上:
```
c uma b
((b @@ a) @ a) @ b
a := c
```
让我们看看将它们连接起来会发生什么。
## 3 位加法器
我们可以构建一个 3 位加法器,由三个 `maj` 组成,将进位通过 `a` 线向上传播,最后的 `@` 将进位输出放在 `c3` 上,最后一行 `uma` 重置所有暂存线:
```
c0 = 0
c3 = 0
(b2 maj (b1 maj (b0 maj c0))) @ c3
a2 a1 a0
(( . uma b2) uma b1) uma b0
c0
```
这是一个完整的 3 位加法器,位于一个单一的 2D 表达式中,并且还能自我清理。`b` 线现在持有 `a + b`,`c3` 持有进位输出,而 `c0` 和 `a` 线完全恢复原样。(将 `c0 = 1` 就能免费得到 `a + b + 1`。)注意两行相互镜像。上面一行向右嵌套到 `b0` 并将进位向上传播;下面一行向左嵌套到 `b2` 并展开回去。共享的 `a1` 和 `a0` 标签是枢轴点,每一个服务于其上方的 `maj` 和其下方的 `uma`,而 `.` 是一条垂直管道,代表正上方的任何线,这里是 `a2`。进位链垂直拼接:反计算触及计算,就像在谜题中一样。
## 其他空间语言快速浏览
以下是几种空间语言的快速浏览。
### Befunge
2D 语言的鼻祖(Befunge,1993):指令指针在网格上物理移动,`>v<^` 控制其方向。Hello world 在墙壁上反弹:
```
> v
v "Hello World!"<
> :v
^,_@
```
### Orca
Orca 是一个实时编码音序器,其中每个字母都是读取其邻居的运算符:`A` 将其左侧的值和右侧的值相加,并将结果直接写在其下方。听起来熟悉吗?
```
1A2
.3.
```
### Racket #2d
一个真实、已发布的语言扩展:Racket 的 `#2dcond` 是一个在二维空间中求值的 `cond`。左列测试 `b`,顶行测试 `a`,两者都为真的那个单元格运行。
```racket
#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
Hexagony 运行在尖顶六边形网格上,就像你可能注意到的某个背景一样。六个指令指针从六个角开始。Hello world:
```
H ; e ; l ; d ; * ; r ; o ; W
l ; ; o ; * 4 3 3 ; @ . > ; 2 3 < \ 4 ; * /
```
### 梯形逻辑
梯形逻辑是 PLC 运行工厂(并且,如果正确实施,鸡舍门)的方式:每一级是一个继电器电路,从轨到轨读取,并行分支是 OR。这个经典的自锁级计算 `Run = (Start OR Run) AND (NOT Stop)`:
```
--+----[ ]--+----[\]----( )
| Start | Stop Run
+----[ ]--+
Run
```
还有一项荣誉提名给有史以来最成功的空间语言:电子表格,其中每个公式都位于 2D 坐标中并读取其邻居。
## “这就是水”
如果你认识我,你就知道我喜欢提起萨丕尔-沃尔夫假说,该假说认为语言塑造了你的思维方式:
- 一些澳大利亚原住民语言没有左和右的词汇,只有基本方向,所以他们的说话者总是知道北在哪里。
- 俄语有两个不同的蓝色词汇,所以俄罗斯人在区分两种蓝色时,当色差跨越那个词汇边界时,速度更快。
- Haskell 是纯粹函数式的,所以你开始在你去的任何地方识别出 monad。诸如此类。
同样,我相信技术塑造了哪些语言出现。
- 终端迫使我们从左到右编写命令,所以我们水平地而不是垂直地思考代码。
- 大多数键盘是为英语和标准 ASCII 符号构建的,所以我们用这些符号思考 😊。
意识到我们的偏见是一种合理的脑力锻炼,至少大卫·福斯特·华莱士是这么认为的,他发表了一篇名为《这就是水》的毕业典礼演讲。尽管他的演讲更少关于量子电路中的辅助比特反计算,而更多关于成长和富有同情心,但你明白了我的意思。通过挑战我们的习惯,我们可以探索新的想法。
相似文章
7行代码,3分钟:实现一种编程语言(2010)
本文介绍了一种基于 Lambda 演算的图灵完备函数式语言的极简 7 行解释器,展示了 eval/apply 设计模式。
混合视觉与文本代码
本文介绍了Hybrid ClojureScript,一种允许开发者混合视觉与文本语法用于领域特定表达式的编程语言,保持了可组合性和静态推理能力。
对 APL 等数组语言的有原则性重新思考
本文提出了一种有原则性的方法来重新思考 APL 等数组语言,通过将变量建模为输入维度的函数,旨在相较于传统方法提高可读性和错误检查能力。
@vintcessun: 程序不是一行行代码,而是一张二维地图?Grid Programs 颠覆了计算模型的直觉:指令在网格上任意排列,指针靠上下左右移动来控制流程,完全不用变量名。为什么重要?这为可视化编程、类细胞自动机硬件和代码混淆打开了全新设计空间,且已证明…
Grid Programs 提出了一种二维网格编程模型,指令在网格上排列,指针通过上下左右移动控制流程,无需变量名,已被证明图灵完备,为可视化编程、细胞自动机硬件和代码混淆开辟了新的设计空间。
让语言模型学会用代码思考
本文介绍了 ThinC(Thinking in Code,用代码思考)框架。在该框架中,语言模型在简短的自然语言规划步骤后,仅使用代码块进行推理,在数学基准测试中优于现有的工具集成推理基线。