表格化编程:一种表达性计算的新范式
Tabular Programming: A New Paradigm for Expressive Computing

原始链接: https://sam.elborai.me/articles/tabular-programming/

作者正在探索一个新颖的编程环境,专为类似 Dirtywave m8 tracker 的最小化、便携式硬件而设计。这个环境使用表格界面,代码被组织成函数,以行表示,具有固定的列:名称(NAME)、输入(IN)、5 个表达式(EXPRESSIONs)和输出(OUT)。这种表格结构和表达式限制鼓励创建原子级的、可组合的函数。 目标硬件是 Teensy 4.1,配备小屏幕和仅 8 个按钮(方向键、A/B 键、START/SELECT 键),无需键盘。导航和编辑通过上下文菜单中的选择进行,而不是通过打字,从而最大限度地减少语法错误。 该概念基于类似 Forth 的基于栈的模型,数据流直观明确。演示场景特效,例如等离子体和隧道效果,被用作示例。一个可运行的网络原型验证了核心概念,展示了在有限硬件上进行创意应用(如像素艺术编辑器)的潜力。目标是创建一个硬件和软件协同工作的集成系统。

Hacker News 上的一个帖子讨论了“表格编程:一种表达性计算的新范式”。原帖获得了 13 分和 3 条评论。一位名为 turtleyacht 的用户指出了一种相关的概念,即 2002 年的“面向表格的编程”,并链接到之前 Hacker News 上的讨论。原发帖人 dgellow 认为这两个概念尽管名称相似,但并不相关。turtleyacht 随后建议“控制表”作为另一个可能的联系,并强调了该帖子的关键特性:参数作为函数,预先定义依赖关系。该帖子还提供了使用表格进行不同类型编程的其他概念。最后还有一个简短的 AI 初创公司学校广告。
相关文章
  • 等宽网页 2024-08-29
  • (评论) 2025-03-22
  • (评论) 2024-03-23
  • (评论) 2024-07-02
  • Zest:一种用于构建灵活易读系统的编程语言 2025-03-19

  • 原文

    I've been exploring the m8 Dirtywave tracker for a few months now, and it's been a fascinating experience. If you're not familiar with it, the m8 is a portable music sequencer built on the Teensy platform that combines a minimal 8-button interface with a powerful tabular workflow. What makes it special isn't just its portability or its sound capabilities, but how the hardware and interface work together to create an experience that's simultaneously constrained and liberating.

    When I'm making music on the m8, I don't feel like I'm fighting with the UI or solving puzzles. It's more like intuitive cooking - mixing ingredients, tasting, adapting, and discovering new flavors along the way. I can lay on my couch, pick it up, and within minutes be immersed in the creative process. No re-learning complicated interfaces, no decision paralysis from too many options - just pure creative flow.

    This experience got me thinking about programming environments. Most of our programming tools are designed for desktop workstations with keyboards, mice, and large displays. But what about a programming environment designed specifically for a minimal, portable hardware interface? What would programming look like if it was built around a tabular interface navigated with just a handful of buttons?

    The Core Idea

    I've been conceptualizing a programming environment built around a tabular interface inspired by the m8. The key insight is that while traditional programming involves typing long streams of text, we could instead organize code into a structured table with each function defined as rows with fixed columns:

    1. NAME: The function identifier
    2. IN: Input parameter specification
    3. EXP1-EXP5: Five expression cells (the core of execution)
    4. OUT: Output specification

    At first glance, limiting each function to just five expressions seems incredibly restrictive. But this constraint actually encourages breaking down complex operations into atomic, composable functions - which tends to produce more maintainable code.

    The hardware component is just as important as the software model. The target hardware would be deliberately minimal:

    • Teensy 4.1 microcontroller (same as the m8)
    • 320x240 display
    • D-pad, A/B, START/SELECT buttons (8 buttons total)
    • microSD slot and headphone jack

    You would navigate the table with the D-pad, edit cells with the A button (which brings up a contextual menu), and run your program with START. No keyboard needed - all programming happens on the device itself through selection rather than typing.

    This isn't just about building yet another programming tool - it's about exploring how hardware constraints can lead to new programming paradigms. When you start from the assumption that your interface has just 8 buttons and a small screen, you're forced to rethink fundamental aspects of how programming works.

    The current concept is built on a stack-based model similar to Forth, but that's just an implementation detail. The core innovation is the tabular programming model and how it maps to minimal hardware controls.

    Some interesting properties emerge from this design:

    1. Error Reduction: By selecting from valid options, many classes of syntax errors simply cannot happen
    2. Focused Decomposition: The five-expression limit forces thoughtful function decomposition
    3. Explicit Data Flow: Data flow between expressions is visually explicit in the table
    4. Portability: The entire environment is designed for programming on the go, in casual positions

    Real-World Examples: Demoscene Effects

    To demonstrate that this concept isn't just theoretical, let's explore how a classic demoscene effect - a plasma - would be implemented using this tabular programming model:

    ┌─────────┬─────┬───────┬───────┬───────┬───────┬───────┬─────┐
    │ 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│
    └─────────┴─────┴───────┴───────┴───────┴───────┴───────┴─────┘
    

    Let's break down how this plasma effect works in detail:

    The entry point PLASMA first calls INIT to set up initial state variables, then calls LOOP to start the main execution loop. The INIT function initializes two global variables: TIME set to 0 to track animation time, and BEAT set to 0 to respond to audio input. The arrow symbol () represents assignment to a variable, making the data flow explicit.

    The main LOOP function orchestrates the entire effect by:

    1. Calling TIME+ to increment the time counter
    2. Calling CLR to clear the screen by writing 0 to video memory
    3. Calling CALC to calculate the plasma values
    4. Calling DISP (implied but not shown) to display the frame
    5. Recursively calling itself to continue the animation loop

    The actual plasma calculation happens through a nested loop structure. CALC sets up the initial Y coordinate (0) and calls Y-LOOP. The Y-LOOP function then processes each row of pixels by:

    1. Setting the initial X coordinate to 0
    2. Calling X-LOOP to process that row
    3. Incrementing Y by 1
    4. Checking if Y is less than the screen height (Y<H?)
    5. If so, recursively calling itself to process the next row

    Similarly, X-LOOP processes each pixel in a row by:

    1. Calling PXCALC to calculate the plasma value for that pixel
    2. Calling PLOT to set the pixel
    3. Incrementing X by 1
    4. Checking if X is less than the screen width (X<W?)
    5. If so, recursively calling itself to process the next pixel

    The core math of the plasma effect happens in PXCALC. It:

    1. Takes X and Y coordinates as input
    2. Divides X by 16 to scale it down
    3. Calculates the sine of that scaled X value
    4. Divides Y by 16 to scale it down
    5. Calculates the cosine of that scaled Y value
    6. Adds the sine and cosine values together
    7. Returns this sum as the plasma value (VAL)

    This creates a simple plasma pattern based on the interference of sine and cosine waves. The division by 16 controls the frequency of the waves.

    PLOT takes X, Y coordinates and a value V as input, then:

    1. Adds the current TIME to the value to animate the plasma
    2. Multiplies by the current BEAT value to make it respond to audio
    3. Calls !PIXEL to set the pixel at (X,Y) with the resulting color value

    The TIME+ function increments the TIME variable by 1 and then calls BEAT+ to update the beat value. The audio responsiveness is implemented by BEAT+ and BEAT1+:

    1. BEAT+ reads the audio input level (AUDIO) and checks if it's above a threshold (THRSH>?)
    2. If it is, it calls BEAT1+ to increase the beat value
    3. BEAT1+ adds 16 to the current beat value, limits it to a maximum of 127, and stores it back in the BEAT variable

    This creates a visual "pulse" effect when audio input is detected, as the plasma pattern will get brighter or more saturated.

    What's particularly fascinating about this implementation is how it manages data flow using the stack. Between each expression cell, data implicitly moves via the stack. For example, in the PXCALC function:

    │ PXCALC  │ X Y │ X 16/ │ SIN   │ Y 16/ │ COS   │ +     │ VAL │
    

    The stack operations would be:

    1. Start with X and Y on the stack
    2. X 16/: Pop X, divide by 16, push result (let's call it X')
    3. SIN: Pop X', calculate sine, push result (sin(X'))
    4. Y 16/: Pop Y, divide by 16, push result (Y')
    5. COS: Pop Y', calculate cosine, push result (cos(Y'))
    6. +: Pop cos(Y'), pop sin(X'), add them, push result (sin(X') + cos(Y'))

    The final value on the stack is the plasma value that gets returned as VAL.

    This illustrates how the stack implicitly connects operations within a row, while the explicit function calls (like X-LOOP calling PXCALC and then PLOT) handle data flow between rows.

    To execute this tabular code, a virtual machine would need to:

    1. Maintain a data stack and a return stack
    2. Process each expression cell from left to right within a row
    3. Handle function calls by pushing the current position to the return stack and jumping to the new function
    4. Support basic stack operations, arithmetic, conditionals, and variable storage
    5. Provide hardware abstraction for screen access, audio input, etc.

    The beauty of this approach is that the VM can be quite simple while still supporting powerful programs. The tabular structure adds a layer of organization that makes the code more readable and navigable than traditional Forth, while still leveraging the power of stack-based programming.

    Here's another example of a classic demoscene effect - a 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│
    └─────────┴─────┴───────┴───────┴───────┴───────┴───────┴─────┘
    

    The tunnel effect works by precalculating angle and distance lookup tables (GEN-LUT, ANGLE, ANG-X, DIST, DST-X) and then using those tables to map each screen pixel to a position in a texture pattern. The angle (ATAN2) and distance (HYPOT) calculations would normally be expensive operations, but by precalculating them into lookup tables, the effect can run at full speed even on limited hardware.

    What's impressive about these examples is how they demonstrate complex mathematics and visualization techniques in a highly constrained programming model. Despite the five-expression limit per function, these programs can create sophisticated visual effects with smooth animation and even audio responsiveness.

    While demoscene effects are a great way to demonstrate the capabilities of this system, I'm more excited about its potential for creative applications like pixel art editors, music tools, or interactive storytelling environments.

    Imagine a pixel art painting program implemented in this tabular format. The main structure might look something like this:

    ┌─────────┬─────┬───────┬───────┬───────┬───────┬───────┬─────┐
    │ 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│
    └─────────┴─────┴───────┴───────┴───────┴───────┴───────┴─────┘
    

    The real power would come from how naturally the hardware maps to the application. The D-pad could move your cursor or navigate UI elements, the A button would draw or select, and B would undo or cancel. The entire interface would be designed around the constraints of the hardware, making it feel natural and intuitive rather than limited.

    One of the challenges with this approach is scaling beyond simple demos. To address this, I've been thinking about a hierarchical organization model inspired by the m8's structure. The m8's navigation system is quite elegant - it divides the interface into "Views" that can be navigated by holding SHIFT and using the direction keys, with a helpful mini-map in the corner showing your location.

    The context-sensitive menu system would be a crucial part of making this environment work with so few controls. When you press the A button to edit a cell, you would see a menu of valid options for that context. This eliminates syntax errors and makes discovery of available functions organic.

    For example, when editing an expression cell, the menu might show available functions, variables, constants, and operators. The options would be filtered based on the current context, so you only see what's relevant. This approach makes programming feel more like navigation than typing.

    What excites me most about this concept isn't just the software model, but how the hardware and software could work together when designed as an integrated system. The m8 succeeds because it isn't just tracker software running on portable hardware - it's a complete experience where every aspect of the hardware and software are designed to work together.

    Similarly, this tabular programming environment isn't just about cramming programming capabilities into a Game Boy-like device. It's about rethinking programming from the ground up for a specific hardware context. The result would be something that doesn't feel constrained when you're using it - it feels natural and intuitive for its purpose, even if it looks strange and completely foreign at first.

    This tabular programming concept opens up many possibilities for exploration. By rethinking how programming can work within tight hardware constraints, we might discover new approaches that actually enhance rather than limit creative expression. I'm sharing this idea because I believe there's something genuinely interesting in the intersection of minimal hardware interfaces and structured programming models.

    I currently have a working but pretty bare bone web prototype, to validate the general concept — the Forth-like interpreter works, and can render examples mentioned in this article.

    联系我们 contact @ memedata.com