So Ren-C had a pretty exciting first 3 months of 2024, and then started to drop off, until I wasn't working on it at all.
I'll try and sum up what's gone on, and where things might realistically go from here.
Rapid Development: January March
Binding Paradigm Shift
The arrival of @bradrn who had been investigating "R" directed attention back to the central language issue of how binding works at all (if it can be made to work). This prioritized revisiting "Pure Virtual Binding" with fresh eyes: Pure Virtual Binding II
I feel the premises are sound. But if you buy into the methodology, then there are many consequences that will seem more alien than usual to historical Rebol programmers. BLOCK!s are not inert in the evaluator, but rather evaluate to a version of the BLOCK! with binding applied... so (blk: [a b c])
is distinct from (blk: '[a b c])
where the latter uses quoting to suppress the evaluation. And since quoting suppresses any binding, (x: 10, y: get 'x)
won't work because the 'x
term drops the quote and stays unbound, meaning you have to use the new VAR-WORD! type and write (x: 10, y: get $x)
.
The epicycles of this new world touch every aspect of the design--forcing "binding awareness" to the forefront of dialect authors and casual users. I wish there were an easier answer, but "implicitly paste unique binding pointers on words in waves" is being proven to be non-composable sufficiently well by other Redbol implementations. It doesn't work meaningfully, so they simply write anything non-trivial as C or Red/System.
I managed to get the system to run well enough to research the design. It is very half-baked, and every few minutes I work with it triggers the need for a new meditation in the Binding category. But as a positive sign, the new model made it possible to add a LET combinator to UPARSE, and there are other longstanding problems that get solved.
Isotopes Shape Up More Clearly
We now use a more consistent terminology where things like null are called "antiforms" instead of "isotopes" (e.g. the null antiform is one of the three isotopes of the WORD! null).
Some other shuffles were made, like saying that void is the antiform word of ~void~, and flipping "nothing" to the antiform of BLANK!. This was based on the realization that there wasn't really all that much value in trying to make voids some special magic type that wasn't an antiform and couldn't be put in blocks, just so its quoted form could be a lone apostrophe.
Breaking Through the 64 Fundamental Types Barrier
Since the dawn of Rebol, the use of a 64-bit bitflag for type checking has meant there can be only 64 types. We need more, and fully generalized typechecking wants to be able to test not just for fundamental types but also for predicates (like if something is EVEN? vs. just if it's an INTEGER!).
To keep the system from slowing down to a glacial pace by type checking through function calls, I hacked together an implementation of what I called "intrinsics". These functions are faster to call than ordinary natives, helping make type checking faster, but also other simple function calls.
The New SIGIL! Type
For a while there was a concept of tolerating "Weird Words" (e.g. with spaces in them) by putting them in vertical bars, |like this|
. That allowed a few new interesting "plain WORD!s" like ::
to exist because you could accomplish their "weird" forms with vertical bars like a SET-WORD! form as |::|:
.
This turned out to be a bad idea. So I simplified with a new type called the SIGIL!... with one for each decoration you could put on WORD!s and arrays. These SIGIL!s only have a plain form, and have baked in evaluator behavior. This also solves a problem elegantly, where you can write code like (x: '$abc, if '$ = sigil of x [...])
API Antiform Splicing Solved
Having the @
symbol be a SIGIL! with built-in evaluator behavior finessed another longstanding problem that having it be a WORD! bound to a reassignable function couldn't do, which was to reconstruct antiforms in API splices, e.g.:
Value* val = rebValue("~true~"); // quasiform evaluates to antiform (logic)
// rebElide("if", val, "[print {Error: no antiforms in source code blocks}]");
rebElide("if @", val, "[print {Special loophole for @ makes this work}]");
The @ sigil can be given a special baked-in behavior--specific to the API--allowing it to preserve and reconstitute the antiform state. This helps bridge the fact that val
above isn't a Rebol WORD!, but a C variable, but still give the indirection that a word would have. (Note that splicing antiforms as quasiforms would be too permissive, because you can't be certain that your splices are in code that is evaluative... it would be like saying compose [1 (null) 2]
gives you [1 ~null~ 2]
instead of raising the intended error.)
Resolving this major sticking point in the API was a big relief.
String Interpolation Solved?
Whenever you are passing around a BLOCK! you may be passing along its environment, to which it holds a permanent reference (preventing that environment from GC'ing). But even more costly would be if every string had to carry around an environment...which also meant anyone you passed a string to gets access to environments that may have incidentally been put on that string.
The solution seems a bit obvious in retrospect: if you want to do interpolation on a string, put it in something like a BLOCK!. Like I say, "a lot better than starting to worry about having to quote your strings to suppress binding!"
April : Things Start to Stall
Coming to Terms with FENCE! and Brace Incompatibility
For the past few years I'd been second-guessing the idea that plain braces should be used for strings, and that they should instead be another array type called FENCE!. Comparing Rebol to the JSON it inspired made it seem like a thing that once seen could not be unseen.
Thinking through the natural alternatives for string representation made me think there could be an asymmetric pair of delimiters made by -{
and }-
. Not only that, the pattern could be extended with more dashes.
obj: {x: 10, y: "obj would be an object now"}
print -{Hello new string "World"}-
print -{Hello single } brace-containing string}-
print --{Hello compound }- brace-containing double dash string}--
It might seem this would be a relatively simple-to-implement change compared to something like binding. But it threw a wrench into things for many reasons:
-
Historical binding was always complete garbage, whereas single-braced strings were actually touted as a pleasing aspect of the language (including by me).
-
This would be the first truly non-backwards-compatible LOAD change to Ren-C, meaning that either some special way to signal which interpretation would be needed, or the work in the Rebol2 compatibility module would go to waste.
-
The idea of a single simple-switch covering this narrow issue always raises the question of whether you want a more generalized hook that you can give before loading. Red offers such a thing as
system/lexer/pre-load
which is just a function that transform source UTF-8 before you load it, though claims it should not be abused -
While I thought I could hack through a prototype with a kind of pre-load functionality using the existing bootstrap executable, trying to pre-empt the LOAD process exposed so many bugs in the R3-Alpha module implementation that it was basically impossible.
-
-
Adding to the heaviness of the change, the concept that single-brace FENCE! creates an OBJECT! under evaluation just brings to attention the question of how binding works in such cases... e.g. does it make sense that
{x: 10, y: y + 20}
would try to make an object in which Y is adding 20 to the as-yet-to-be-defined Y field of the object being produced, necessitating you to writeeval compose ${x: 10, y: (y) + 20}
to capture an external Y? Does that EVAL treat fences different than other array types, and if so should it treat groups different from blocks?
I Went Down The Rabbit Hole Of Updating The Bootstrap Executable
The makefile generation and other "prep" code has been building using a version of Ren-C from December 2018. It's been somewhat impressive that techniques in a bootstrap-shim
file have been able to largely twist the language to modern conventions from within the language itself. (This continues the tradition from r2-forward
which made Rebol2 act more like R3-Alpha without having to change the EXE itself, though the adaptations were much less radical and done without Ren-C's high-leverage function composition tools.)
But hitting the bugs in pre-empting the LOAD process made me realize that it was time to make new bootstrap executables that fixed those bugs, and supported a mechanism for the new string format (and perhaps even add the FENCE! type).
So long as I was doing that, I could fold most of the bootstrap-shim features into the executable itself, for better and cleaner performance. While doing so I could take that time to assess whether all the improvements had actually turned out to be improvements or not...reverting anything that had turned out to be bad. I could also make the bootstrap EXE's PARSE compatible with modern UPARSE, meaning the only Rebol2-style parsing we'd have would be in the Redbol emulation.
I also undertook modernizing the C code itself, where old names like RELVAL
or REBSER
could be replaced with newer stylization like Cell
and Series
. This made it easier to read and compare older and newer code, and paste between them.
New &
Sigil, but No Silver Bullet For Types
Opening up the number of fundamental types allowed bringing in a new sigil (&[bl o ck]
, &word
, &(gr o up)
etc.) which could be used for types. While some ideas were thrown around, no truly great coherent ideas for what Rebol's type system should be came up.
May July : Not Really Thinking About Rebol
I had a lot going on personally, and confronting Rebol's hard problems head-on was very wearying and not something I felt like doing (or even able to do).
Also wearying is that the world doesn't stand still. The tools you use become deprecated and new versions have new flags and frustrations to deal with. Every month (week?) you step away means you'll be coming back to something that broke, especially when you try to keep things running on as many platforms as Ren-C does.
So I found it easier to mess around with NixOS and discover more about what state it's in these days, watch YouTube math videos and learn about Manim, ask AI to turn my old unfinished clips into complete songs, go to improv... basically anything but worry about the design of an esoteric language.
(I also have a "part-time job" as a product reviewer, that I was picked for on the basis of having written good reviews on Amazon for some years. It doesn't pay directly, but I get free stuff to write reviews for: listening on these $279-list-price earbuds right now.)
August : Now What?
I'm back in Florida now, and settling into hermit mode...possibly for a couple of months, or maybe all winter. More in the mood for doing some development.
I've synchronized Ren-C with all the updates of the compilers and tools, and made a new Linux development VM based on Ubuntu 24.04 LTS (since the last Long-Term-Support release I'd been using expired, long-term doesn't actually feel all that long).
And I'm now taking the important step of sitting down to write a status update. It's good to remember the nifty stuff that happened earlier in the year, and consciously reflect on what has been blocking the desire to work on it since then.
I sure wish I could make some conservative choices and create a mimimum viable product, without needing to single-handedly try and solve everything up front with the Ren-C artifact.
Yet the sad fact is that when it comes to just "getting the idea out there", that ship sailed long ago with Rebol2. From a design level, it found just enough that would stick to the wall. But it was enough to inspire JSON, and then spawn a bunch of bus-factor-one derivative projects.
While Ren-C took R3-Alpha's Second-System Effect to new heights of pathology, it has the merit of being full of ideas I find interesting. Meanwhile in over a decade, Red's "getting the idea out there, part 2" mission hasn't even accomplished an artifact that achieves Rebol2 parity for our modern faster-evolving ecology... despite being relatively unambitious about changing the language. And I don't personally find much interesting about it (except for some of the stuff @hiiamboris does)
Grafting one or two of the cool ideas onto R3-Alpha or Red, while leaving binding and the other big picture issues unsolved, isn't something the world needs (or can be made to want, nor should it). So I've dug my own hole: too many nifty concepts, but not a complete enough picture to deploy as a system.
Could I do small bite-size YouTube shorts (or similar) that demonstrate interesting ideas, without actually trying to sell people on an artifact that implements those ideas? I'm not sure... but there are people who will watch entire documentaries on things like TempleOS without feeling they've wasted their time, even though they don't intend to ever run it.
Step One Is Write Update Post. Step Two Is...
I've actually had some topics I've wanted to write on in the last few weeks.
But I felt that since I hadn't said anything for a while, I needed to write a status post and speak some about what's been on my mind.
Ok...
I'm going to do some low-pressure tinkering just to get back to making commits and posts and see what grabs me.
I'll update here with any more thoughts. Certainly I think that making the canon "Rebol: The Movie" documentary is something I'm uniquely qualified to do. Maybe if I combined history, stories of the cast of characters and personalities, along with summarized design arguments and critiques it would be enough that the software-writing-AI of the future (or...Thursday?) could sort it out. If not, at least entertain.