Genesis of Beans
A new paradigm
I've been thinking since the beginning of 2023 about a new language I've named "beans". The name is kind of a joke, but also I think beans are a nice illustration of the small, simple nature of the language.
Beans isn't really a specific programming language as much as it is a programming paradigm that may not even be Turing complete. So how it programming in beans different from regular programming?
In most programming languages, you define a series of instructions that are are executed, maybe displaying something on the screen or using any number of external devices, taking user input, etc. To understand beans, you must adopt a completely different mindset. Each bean on it's own is an isolated entity. You can think of it as a biological cell, a plant, an animal or even a person. None of those things operate by running through a series of instructions with omnipotence, rather, they work with other entities giving and taking. The same is true of beans. In fact, I could imagine an implementation of beans which involves just people assigned roles and passing notes between each other. That's exactly how you should think of beans. Note that this kind of system is parallel and modular, the two greatest benefits, which do result in some extra complexity.
In this article I hope to demonstrate one implementation of beans, which hopefully will inspire you to think differently about computing.
Imagine a grid, and imagine that within each cell of the grid, you can put a machine. Each machine is capable of passing notes to neighboring machines. Upon receiving a note, a machine is allowed to do any amount of computation and pass any number of notes to neighboring cells itself.
In this implementation you'll see this language becomes something like cellular automata mixed with a circuit board. But as you'll see, it will work a bit differently than either of those.
With a given system one could imagine many possibilities as to what the machines could do. I will lay out what I think are some good potential starting machines which are most useful.
For the sake of being concrete, let's assume that each note contains one single byte or a number for 0-255, represented in hexadecimal as two characters, like: 0a, ff, ee
First we'll start with a couple of primitives which allow us to consider external input and output. Like stdin/stdout, mouse clicks, and updates to the screen.
Ports cannot be added anywhere, but live at the edges of the program to indicate that they are being sent in and out of the system.
Note that each i/o port can only send one note/byte at a time, so multiple ports may be required to interface with some devices, or some serialization may be required.
We'll assume we can receive input from any kind of port to play with the given examples.
This machine is quite simple, it takes any note given and pushes it forward. Giving a conveyor belt an orientation then is useful to be able to point the note in a given direction to route it between other beans.
Now that the primers are out of the way, we can get to the fun stuff. A counter is a simple machine that can be used to add up any number of notes from the left. Sending a note from behind triggers the current value to be output forward.
But because notes only go up to `ff`, the counter must reset to 0 each time it goes above that number. Whenever this happens, a note with `01` is sent to the left to indicate the reset occurred.
Because of this, we can link up multiple counters together to act as a counter for larger numbers.
Pros and cons of being Asynchronous
Dealing with race conditions
While the system is quite interesting, there are some additional complexities. One of them being the system is asynchronous, and race conditions are inevitable. There are some ways to enforce synchrony, like just making some conveyor belts longer than others, but that just couples the operation tightly with the design and layout.
Another way to ensure synchronous operation is to abstract it away. Assuming each machine is itself an almost boundless computer, we can embed prebuilt systems within it, (as long as they terminate and don't run forever). One could imagine a recursive bean definition system which allows building new bean types by composing other beans.
In doing so, we can ensure "first in first out" operation within any given bean, so it will process inputs synchronously to provide better concrete execution at the highest level.
Benefits of parallel execution
By putting constraints on the system so that there is no access to external memory or devices, cells are almost isolated processes, only interfacing with the outside world through it's predefined inputs and outputs.
This means cells can be executed on different processes and cores arbitrarily, or even on entirely different computers, as long as they can send messages.
So as alluded to, I'm just at the beginning stages towards fleshing this out, while I have created a couple of prototypes already. (As of May 2023)
My ideal implementation is built entirely in uxntal with an interactive programming environment, but this is still to come.
My current implementation is a WIP, but feel free to follow along :) beans.