表格化编程:一种表达计算的新范式
sam.elborai 首页 文章 系列 标签 打开配色方案设置 浅色 深色 系统
表格化编程:受限环境中表达计算的新范式
我探索 m8 Dirtywave tracker 已经几个月了,这是一个非常有趣的体验。如果你不熟悉它,m8 是一个基于 Teensy 平台的便携式音乐音序器,它将一个极简的 8 按钮界面与强大的表格化工作流程结合在一起。它的特别之处不仅仅在于其便携性或声音功能,还在于硬件和界面如何协同工作,从而创造出一种既受限制又解放的体验。
当我在 m8 上制作音乐时,我不觉得我在与 UI 作斗争或解决难题。它更像是直觉性的烹饪——混合食材、品尝、调整和发现新的味道。我可以躺在沙发上,拿起它,在几分钟内沉浸在创作过程中。无需重新学习复杂的界面,没有太多选择造成的决策瘫痪——只有纯粹的创作流程。
这种体验让我想到了编程环境。我们的大多数编程工具都是为带有键盘、鼠标和大显示器的桌面工作站设计的。但是,专门为极简、便携的硬件界面设计的编程环境呢?如果编程是围绕一个仅用几个按钮导航的表格界面构建的,会是什么样子?
核心思想
我一直在构思一个基于表格界面的编程环境,其灵感来自 m8。关键的见解是,虽然传统的编程涉及键入长文本流,但我们可以将代码组织成一个结构化的表格,其中每个函数定义为具有固定列的行:
- NAME:函数标识符
- IN:输入参数规范
- EXP1-EXP5:五个表达式单元(执行的核心)
- OUT:输出规范
乍一看,将每个函数限制为仅五个表达式似乎非常严格。但是,这种限制实际上鼓励将复杂的操作分解为原子的、可组合的函数——这往往会产生更易于维护的代码。
硬件组件与软件模型同样重要。目标硬件将是刻意极简的:
- Teensy 4.1 微控制器(与 m8 相同)
- 320x240 显示器
- D-pad、A/B、START/SELECT 按钮(总共 8 个按钮)
- microSD 卡槽和耳机插孔
你可以使用 D-pad 导航表格,使用 A 按钮编辑单元格(这将调出一个上下文菜单),并使用 START 运行你的程序。无需键盘——所有编程都通过选择而不是键入在设备本身上进行。
这不仅仅是构建另一个编程工具——而是探索硬件约束如何导致新的编程范式。当你从你的界面只有 8 个按钮和一个小屏幕的假设开始时,你会被迫重新思考编程工作方式的基本方面。
当前的概念建立在类似于 Forth 的基于堆栈的模型之上,但这只是一个实现细节。核心创新是表格化编程模型以及它如何映射到极简的硬件控制。
这种设计出现了一些有趣的特性:
- 减少错误:通过从有效选项中进行选择,许多类型的语法错误根本不会发生
- 专注的分解:五个表达式的限制迫使人们认真思考函数分解
- 显式数据流:表达式之间的数据流在表格中是视觉上显式的
- 便携性:整个环境专为随时随地、以休闲姿势进行编程而设计
真实世界的例子:Demoscene 特效
为了证明这个概念不仅仅是理论上的,让我们探讨一下如何使用这种表格化编程模型来实现一个经典的 demoscene 特效——plasma:
┌─────────┬─────┬───────┬───────┬───────┬───────┬───────┬─────┐
│ NAME │ IN │ EXP1 │ EXP2 │ EXP3 │ EXP4 │ EXP5 │ OUT │
├─────────┼─────┼───────┼───────┼───────┼───────┼───────┼─────┤
│ PLASMA │ void│ INIT │ LOOP │ │ │ │ void│
│ INIT │ void│ 0 │ →TIME │ 0 │ →BEAT │ │ void│
│ LOOP │ void│ TIME+ │ CLR │ CALC │ DISP │ LOOP │ void│
│ CLR │ void│ 0 │ VRAM! │ │ │ │ void│
│ CALC │ void│ 0 │ 0 │ Y-LOOP│ │ │ void│
│ Y-LOOP │ Y │ 0 │ X-LOOP│ Y 1+ │ Y<H? │ Y-LOOP│ void│
│ X-LOOP │ X Y │ PXCALC│ PLOT │ X 1+ │ X<W? │ X-LOOP│ void│
│ PXCALC │ X Y │ X 16/ │ SIN │ Y 16/ │ COS │ + │ VAL │
│ PLOT │ X Y V│ TIME │ V + │ BEAT │ * │ !PIXEL│ void│
│ TIME+ │ void│ TIME │ 1 + │ →TIME │ BEAT+ │ │ void│
│ BEAT+ │ void│ AUDIO │ THRSH>?│ BEAT1+│ │ │ void│
│ BEAT1+ │ void│ BEAT │ 16 + │ 127 MIN│ →BEAT│ │ void│
└─────────┴─────┴───────┴───────┴───────┴───────┴───────┴─────┘
让我们详细分析一下这个 plasma 特效是如何工作的:
入口点 PLASMA
首先调用 INIT
来设置初始状态变量,然后调用 LOOP
来启动主执行循环。INIT
函数初始化两个全局变量:TIME
设置为 0 以跟踪动画时间,BEAT
设置为 0 以响应音频输入。箭头符号 (→
) 表示变量赋值,使数据流显式化。
主要的 LOOP
函数通过以下方式协调整个效果:
- 调用
TIME+
以增加时间计数器 - 调用
CLR
以通过将 0 写入视频内存来清除屏幕 - 调用
CALC
来计算 plasma 值 - 调用
DISP
(隐含但未显示)以显示帧 - 递归调用自身以继续动画循环
实际的 plasma 计算通过嵌套循环结构进行。CALC
设置初始 Y 坐标 (0) 并调用 Y-LOOP
。然后,Y-LOOP
函数通过以下方式处理每个像素行:
- 将初始 X 坐标设置为 0
- 调用
X-LOOP
以处理该行 - 将 Y 递增 1
- 检查 Y 是否小于屏幕高度 (
Y<H?
) - 如果是,则递归调用自身以处理下一行
类似地,X-LOOP
通过以下方式处理一行中的每个像素:
- 调用
PXCALC
以计算该像素的 plasma 值 - 调用
PLOT
以设置像素 - 将 X 递增 1
- 检查 X 是否小于屏幕宽度 (
X<W?
) - 如果是,则递归调用自身以处理下一个像素
plasma 效果的核心数学运算发生在 PXCALC
中。它:
- 将 X 和 Y 坐标作为输入
- 将 X 除以 16 以缩小比例
- 计算该缩放的 X 值的正弦值
- 将 Y 除以 16 以缩小比例
- 计算该缩放的 Y 值的余弦值
- 将正弦值和余弦值加在一起
- 将该总和作为 plasma 值 (
VAL
) 返回
这会创建一个基于正弦和余弦波干扰的简单 plasma 模式。除以 16 控制波的频率。
PLOT
接受 X、Y 坐标和一个值 V 作为输入,然后:
- 将当前
TIME
添加到值以动画 plasma - 乘以当前的
BEAT
值以使其响应音频 - 调用
!PIXEL
以使用结果颜色值设置 (X,Y) 处的像素
TIME+
函数将 TIME
变量递增 1,然后调用 BEAT+
以更新节拍值。音频响应能力由 BEAT+
和 BEAT1+
实现:
BEAT+
读取音频输入电平 (AUDIO
) 并检查其是否高于阈值 (THRSH>?
)- 如果是,它会调用
BEAT1+
以增加节拍值 BEAT1+
将 16 添加到当前节拍值,将其限制为最大值 127,然后将其存储回BEAT
变量中
当检测到音频输入时,这会创建一个视觉“脉冲”效果,因为 plasma 模式会变得更亮或更饱和。
这种实现特别有趣的是它如何使用堆栈管理数据流。在每个表达式单元格之间,数据通过堆栈隐式移动。例如,在 PXCALC
函数中:
│ PXCALC │ X Y │ X 16/ │ SIN │ Y 16/ │ COS │ + │ VAL │
堆栈操作将是:
- 从堆栈上的 X 和 Y 开始
X 16/
: 弹出 X,除以 16,推送结果(我们称之为 X')SIN
: 弹出 X',计算正弦值,推送结果 (sin(X'))Y 16/
: 弹出 Y,除以 16,推送结果 (Y')COS
: 弹出 Y',计算余弦值,推送结果 (cos(Y'))+
: 弹出 cos(Y'),弹出 sin(X'),将它们相加,推送结果 (sin(X') + cos(Y'))
堆栈上的最终值是作为 VAL
返回的 plasma 值。
这说明了堆栈如何隐式地连接一行中的操作,而显式函数调用(例如 X-LOOP
调用 PXCALC
,然后调用 PLOT
)处理行之间的数据流。
要执行此表格化代码,虚拟机需要:
- 维护一个数据堆栈和一个返回堆栈
- 从左到右处理一行中的每个表达式单元格
- 通过将当前位置推送到返回堆栈并跳转到新函数来处理函数调用
- 支持基本堆栈操作、算术、条件和变量存储
- 为屏幕访问、音频输入等提供硬件抽象
这种方法的优点在于,虚拟机可以非常简单,同时仍然支持强大的程序。表格结构增加了一层组织,使代码比传统的 Forth 更易于阅读和导航,同时仍然利用基于堆栈的编程的强大功能。
这是另一个经典 demoscene 特效的示例——tunnel:
┌─────────┬─────┬───────┬───────┬───────┬───────┬───────┬─────┐
│ NAME │ IN │ EXP1 │ EXP2 │ EXP3 │ EXP4 │ EXP5 │ OUT │
├─────────┼─────┼───────┼───────┼───────┼───────┼───────┼─────┤
│ MAIN │ void│ INIT │ LOOP │ │ │ │ void│
│ INIT │ void│ 0 │ →TIME │ GEN-LUT│ │ │ void│
│ LOOP │ void│ TIME+ │ CLR │ CALC │ DISP │ LOOP │ void│
│ CLR │ void│ 0 │ VRAM! │ │ │ │ void│
│ GEN-LUT │ void│ 0 │ ANGLE │ 0 │ DIST │ │ void│
│ ANGLE │ Y │ 0 │ ANG-X │ Y 1+ │ Y<H? │ ANGLE │ void│
│ ANG-X │ X Y │ X CX -│ Y CY -│ ATAN2 │ →ANG │ X+ │ void│
│ DIST │ Y │ 0 │ DST-X │ Y 1+ │ Y<H? │ DIST │ void│
│ DST-X │ X Y │ X CX -│ Y CY -│ HYPOT │ →DST │ X+ │ void│
│ CALC │ void│ 0 │ 0 │ Y-LOOP│ │ │ void│
│ Y-LOOP │ Y │ 0 │ X-LOOP│ Y 1+ │ Y<H? │ Y-LOOP│ void│
│ X-LOOP │ X Y │ X Y │ TEX │ PLOT │ X 1+ │ X<W? │ void│
│ TEX │ X Y │ ANG@ │ DST@ │ TIME +│ PTRN │ │ C │
│ PLOT │ X Y C│ X │ Y │ C │ !PIXEL│ │ void│
│ TIME+ │ void│ TIME │ 1 + │ →TIME │ │ │ void│
└─────────┴─────┴───────┴───────┴───────┴───────┴───────┴─────┘
隧道效果的工作原理是预先计算角度和距离查找表 (GEN-LUT
, ANGLE
, ANG-X
, DIST
, DST-X
),然后使用这些表将每个屏幕像素映射到纹理模式中的位置。角度 (ATAN2
) 和距离 (HYPOT
) 计算通常是昂贵的操作,但通过将它们预先计算到查找表中,即使在有限的硬件上,该效果也可以全速运行。
这些示例令人印象深刻的是,它们如何在高度受限的编程模型中演示复杂的数学和可视化技术。尽管每个函数有五个表达式的限制,但这些程序可以创建具有平滑动画甚至音频响应的复杂视觉效果。
虽然 demoscene 特效是演示此系统功能的绝佳方式,但我对其在像素艺术编辑器、音乐工具或交互式故事讲述环境等创意应用中的潜力更感兴趣。
想象一下,以这种表格化格式实现的像素艺术绘画程序。主结构可能如下所示:
┌─────────┬─────┬───────┬───────┬───────┬───────┬───────┬─────┐
│ NAME │ IN │ EXP1 │ EXP2 │ EXP3 │ EXP4 │ EXP5 │ OUT │
├─────────┼─────┼───────┼───────┼───────┼───────┼───────┼─────┤
│ MAIN │ void│ INIT │ LOOP │ │ │ │ void│
│ INIT │ void│ LOAD-│ CANVAS│ UI-INIT│ │ │ void│
│ LOOP │ void│ INPUT │ PROCESS│ RENDER│ SAVE? │ LOOP │ void│
│ INPUT │ void│ JOY │ BUTTONS│ →STATE│ │ │ void│
│ PROCESS │ void│ MODE? │ DRAW │ FILL │ SELECT│ MENU │ void│
│ RENDER │ void│ CANVAS│ CURSOR│ UI │ FLIP │ │ void│
└─────────┴─────┴───────┴───────┴───────┴───────┴───────┴─────┘
真正的力量将来自硬件如何自然地映射到应用程序。D-pad 可以移动你的光标或导航 UI 元素,A 按钮将绘制或选择,B 将撤消或取消。整个界面将围绕硬件的约束进行设计,使其感觉自然直观,而不是受到限制。
这种方法的一个挑战是扩展到简单的演示之外。为了解决这个问题,我一直在考虑一种受 m8 结构启发的层次结构组织模型。m8 的导航系统非常优雅——它将界面划分为可以通过按住 SHIFT 并使用方向键导航的“视图”,角落中有一个有用的迷你地图显示你的位置。
上下文相关的菜单系统将是使该环境在如此少的控件下工作的重要组成部分。当你按下 A 按钮来编辑单元格时,你将看到该上下文中有效选项的菜单。这消除了语法错误,并使可用函数的发现变得有机。
例如,当编辑表达式单元格时,菜单可能会显示可用的函数、变量、常量和运算符。选项将根据当前上下文进行过滤,因此你只会看到相关的内容。这种方法使编程感觉更像是导航而不是键入。
这个概念最让我兴奋的不仅仅是软件模型,而是当硬件和软件设计为一个集成系统时,它们如何协同工作。m8 成功是因为它不仅仅是在便携式硬件上运行的 tracker 软件——它是一种完整的体验,其中硬件和软件的每个方面都旨在协同工作。
同样,这种表格化编程环境不仅仅是将编程功能塞进类似 Game Boy 的设备中。它是关于从头开始为特定的硬件上下文重新思考编程。结果将是你在使用它时不会感到受限制的东西——对于它的用途来说,它感觉自然直观,即使它起初看起来很奇怪且完全陌生。
这种表格化编程概念为探索开辟了许多可能性。通过重新思考编程如何在严格的硬件约束下工作,我们可能会发现实际上增强而不是限制创造性表达的新方法。我分享这个想法是因为我相信在极简硬件界面和结构化编程模型的交叉点上存在着真正有趣的东西。
我目前有一个可用的但非常简陋的 Web 原型,用于验证总体概念——类似 Forth 的解释器可以工作,并且可以渲染本文中提到的示例。 GitHub