Two pointers, two different jobs
Brainfuck and Befunge both have a pointer that moves, and that similarity hides a deep difference. The two languages move different things for different reasons.
In Brainfuck, the program text is a fixed string that is read from start to end, with jumps backward and forward at brackets. A separate tape holds the data, and a data pointer moves along it. So there are two pointers, one for code and one for data, and they live in different places.
In Befunge, the program is laid out on a two-dimensional grid, and the instruction pointer moves across it, one cell at a time, in a direction that the program itself can change. Data mostly sits on a stack, and the grid can also hold data through get and put instructions. There is one pointer moving through code, and the data pointer does not exist.
Where the code lives
On a tape-style language, code is a line. Execution goes forward. A loop is a pair of brackets, and the only way to go back is to hit the closing bracket and jump to the match. The position in the program is a single number.
On a grid, code is a surface. Execution continues in the current direction until something turns it. Position is a pair of coordinates (or more, in higher-dimensional Funge), plus a direction. Loops arise from the geometry itself: a path that leads back to where it started is a loop, which is the idea behind geometric loops. The way grid-based flow works covers this in detail.
Where the data lives
On the tape, data is the tape. It is a row of cells, usually all the same type, with the pointer deciding which one is current. Getting to another cell means walking the pointer there. Data layout is the programmer’s job: where to keep the counter, where the scratch cells go.
In Befunge, numbers are mostly kept on the stack, and so the question of layout disappears for simple tasks. When the stack is not enough, programs store values in unused cells of the grid. The code and data then share a space, which is why dead code regions are sometimes reused for storage, and why a program can in principle rewrite itself.
A same-task comparison
Let us count down from 3, printing each value, using a hypothetical simple version of each language.
In a tape language, you put 3 in a cell, then loop: print the cell, subtract 1, repeat while nonzero. The loop is a bracket pair around the body, and the counter lives on the tape.
In Befunge, the counter lives on the stack. The program pushes 3, prints and decrements it, and the pointer reaches an instruction that sends it back left if the value is nonzero. The loop is a path on the page, not a pair of symbols. A countdown program is a good first example for seeing how that works.
The two programs compute the same thing, yet their shapes are opposite: one is a line with nested structure, the other is a picture with a route.
Edges and boundaries
A tape has ends, or it extends indefinitely in one direction. Moving off the left end is typically an error or a wrap, depending on the implementation. A grid has edges in every direction. Befunge’s grid wraps around, so a pointer leaving the right side reappears on the left, which makes the playfield behave like a surface with no boundary. This toroidal playfield is a design decision, not an accident; it ensures the pointer never falls off.
Dimensions as a dial
The distinction is not strictly two-versus-one. The Funge family treats dimensionality as a parameter. Unefunge puts the program on a line, which comes close to a tape-like layout, though the pointer can still reverse direction. Trefunge dimensions add a third axis. Seen this way, a tape is the one-dimensional end of a series, and a grid is the next step.
The difference between them is not simply one more axis but a change in role. In a Brainfuck-style design, the dimension belongs to the memory. In a Funge-style design, the dimension belongs to the program text. Direction and turning become a major part of the language, with pointer direction and delta vectors replacing the notion of a simple forward step.
Consequences for programmers
- Readability: Tape code is linear and easy to scan, though dense. Grid code can be laid out so that its structure is visible, but it can also hide a path that is hard to follow. A trace on paper, as with tracing by hand, is the usual tool.
- Debugging: On a tape, you inspect cells. On a grid, you follow the path of the pointer.
- Writing: On a tape, you plan memory layout. On a grid, you plan geography, and you may need to route around or past other code.
- Implementation: A tape interpreter needs an array. A grid interpreter needs a way to store a possibly sparse plane, as discussed in the playfield data structure topic.
What the comparison shows
Both models can express any computation in principle. The difference is in what the programmer has to think about. A tape asks you to manage where things are stored; a grid asks you to manage where the flow of control goes. Many people find that learning one makes the other clearer, because you see more precisely which idea belongs to the memory and which to the control flow. For the broader family of related languages, the two-dimensional languages group is the next place to look.