Append-Only Programming (2024)
iafisher.com
iafisher.com
The unison language ecosystem leverages that property to implement distributed computation where the code can be shipped to remote workers in a very fine grained way, but I guess this building block can be used for other ideas (I don't know, didn't quite put my mind into it but sounds very interesting)
The thing about making it explicit is that it means you can see what version is being used just by reading the code, and you can also find usages of a specific version with a simple text search. Without that, it's more likely that maintainers will get stuck relying on more complicated - and therefore brittle - tooling to manage the code.
I kind of doing this with my AoC with my literary programming approach where I add only code to the markdown file that is then processed by the MarkDownC program [1], which takes all the C fragments in de markdown file and puts them in the right order to be compilable, overwriting earlier definitions of functions and variables. So, each markdown file, one per day [2], shows all the steps how I arrived at the solution. I do use a normal editor and use copy-and-paste a lot when making new versions of a certain function.
[1] https://github.com/FransFaase/IParse/?tab=readme-ov-file#mar...
[2] https://github.com/FransFaase/AdventOfCode2024/blob/main/Day...
Imagine something like a Jupyter notebook, with embedded browser widget; you look something up and that info is embedded in notebook. Same with bits of source files, shell commands and output, etc.
Trying to remember how I did something like 6 months ago? Open that notebook and scroll from bottom.
I’m skeptical of llm-take-over-the-world narratives, but I think that also a document like this could be used by them to synthesize summaries, TOCs, etc.
One of these days I’ll get around to implementing this b kind of flow myself. I hope. Someday.
On the other hand, it's not a problem if you start bottom-up, which is a natural style when writing in C; the low-level functions are at the top (and the standard headers included at the very top can be thought of as a sort of lowest-level), while main() is at the bottom.
Build main() by decomposing it into calls to input(), process(), output(). At each step decompose the function you're working on, handing as much of its work as possible off to a child function you haven't yet written. Only question after that is whether you implement those functions depth-first or breadth-first (I'd prefer depth-first).
Maybe procedural programming is now so old it's novel again.
What really hurts readability for me is when the structure is a random mix of top-down and bottom-up. Which seems to be regrettably common in many object-oriented codebases.
"Once @cognition_labs AI Engineer Devin gets good enough, I will have it implement each feature as a series of Pull Requests: - One or more Refactoring PRs - modify structure of existing code but no behavioral change. - A final PR which is "append only" code - no structural change, only behavioral."
Gives you lovely stuff like the Win32 API (introduced in 1993 and still very much a thing!). CreateWindow, CreateWindowEx, CreateWindowExEx (OK, I made that up...), structs with a load-bearing length field, etc. etc. And read some Raymond Chen on the abuse that customers inflict on the more 'private' stuff...
I recall there being a blog post, maybe by Joe Armstrong in which he advocated for having version numbers in functions. CreateWindow_v1 CreateWindow_v2, or you could use the commit hash.
So they had something like GPIOHANDLE_GET_LINE_VALUES_IOCTL, decided to change things around a little, so introduced GPIO_V2_LINE_GET_VALUES_IOCTL.
Although, as the saying goes, the problem with backwards compatibility is that anything that starts backwards stays backwards.
- It would be less challenging if function pointers variables are used instead of function. In this case, the code appended later may override the function variables it needs to fix/change
- Since all the code is there, it is possible to invent some convention to compile/run previous versions without CVS machinery void (*foo)(void)
int main(){
*foo();
}
void foo1(){...}
foo = &foo1; // will be overwritten later
void foo2(){...}
foo = &foo2;
I'm on my phone so I haven't compiled it, but that's the rough idea I got, define lots of versions and then set the version to the latest one.(You can skip the address-of for functions by punning, but reading the types is hard enough already.)
Fair point, I hadn't thought it all the way through. It's also all too easy to assume you can use C++ features or GNU extensions if you're not in the habit of enforcing the standard. For your trouble here are two partial solutions:
---
1. CPP abuse
If I was doing this myself I'd probably just use the preprocessor to make the last function my entry point like
SOURCE=file.c gcc -DLATEST_MAIN=$(grep -E "^[a-zA-Z_].*\(.*\)\s*\{" ${SOURCE} | tail -1 | grep -Po "[a-zA-Z_]\w*(?=\()") ${SOURCE}
and then you can start with int main(void){LATEST_MAIN();};
and append void foo(){...}
...
void bar(){...}
and execution will start from the last defined function (that matches the regex).---
2. Pre-defining non-static functions
If that's cheating then you can redefine "external" functions before the linker has a chance to provide them. For example:
#include <stdlib.h>
int (*foo)(void);
int main(void){exit(0); *foo();}
is a valid program, and you can append void f1(){puts("oh");}
int exit(){foo=&f1;}
and foo will indeed be called. It does require some forward planning if you want to append multiple times, but it shouldn't be so difficult to generate a library full of dummy functions to use.If for some reason I had to solve this problem in real life I would do it with a postprocesing step like your #1 suggestion. But once you're putting code into your Makefile anything goes.
I'm sure you know, but the undefined order is easily fixed by e.g. keeping a priority queue for your appended functions and then each time you add one you can include a constructor that enters it in the queue with a priority corresponding to its desired position.
These days of course we just use git, but there was a day that we could see the progress of a codebase by watching the diffs as they streamed in off the reels ..
10 print "helo"
20 print "world"
10 print "hello"
Worked as expected in both GW-BASIC 1.0 and pcbasic, printing "hello\nworld". Listing the program after loading it only shows the modified line 10.A bit awkward since the BASIC editor/REPL itself can not be used. It would work for writing BASIC using a regular text editor and then just running it with BASIC as an interpreter.
My thoughts in this is to somehow create a system where additional rules or changes to behaviour have marginal cost.
I am interested in the Rete algorithm, a rule engine algorithm. But we could run this sort of thing at compile time to wire up system architecture.
Boilerplate or configuration is an enormous part of programming and I feel there really could be more tools to transform software architecture.
Well, it's ridiculous. IMO, of course but... seriously. One of the greatest (and even joyful) things about being a software developer is that you can change old code. Literally go there, rewrite things, and end up with a new version of code (which is presumably better in some respect).
Art progresses with extreme restrictions. The same way Schoenberg put seemingly absurd restrictions in his music ((very roughly) don't repeat the same note before playing every other note etc...) to create something radically novel, we as software developers can do so to advance our art as well.
[1] This method is the anti-thesis of the common "never rewrite a working program" software development methodology. Here, the experiment is to see what happens if we always rewrite, and never add or modify, i.e. refactors are never allowed, instead if things need changing we need to re-design the whole thing top-bottom with the new understanding.
Anyone telling me to write software using `cat >> foo.c` better come with some receipts.
Sorry, but what? This does not make any sense.
like LLMs
very cool