How about that year? Is everyone looking forward to what the world has in store for 2025? No?
Well, let's forget world news for a minute... and just focus on the code.
There was significantly more development (and corresponding progress) in 2024 than in 2023. Most centrally in binding, but many other advances were made as well.
Good news is that key binding advances have culminated in notable success stories. Although it means incompatibility with basically all prior Rebol/Red "Bindology", that was simply a dead-end. I've reached optimism that this method may ultimately work.
Yet optimism about binding led me to bite the bullet and go ahead with other incompatible changes. It's a bit like realizing you aren't just fixing up a house to sell, but you're going to live there...so, might as well upgrade stuff while it's being ripped open. Of course that digs an even bigger hole: I've been working on a side branch for nearly half a year.
Let me try and quickly show off some good, new things...coming soon to a master branch near you...
Moving to a Mostly-Unbound World...
2024 saw a new shift in Ren-C:
>> x: 10
>> get 'x
** Error: x is not bound
>> get first [x + 1]
** Error: x is not bound
At first, that may seem scary.
But getting rid of a-priori deep walks for binding can actually cure your fears of "missing the wave". A quick example:
>> doubler: func [x] [
let code: copy [add x]
append code to word! "x"
print ["Doubled:" eval code]
]
>> doubler 10
Doubled: 20 ; wait... that *worked*?! ๐ฎ
That would have previously been a helpless situation--to navigate from the "x" string to the X
word, and bind that word in a way that it could find a local variable. But the trick here is that none of the code is bound in advance. The [add x x]
you're constructing sees no difference between the two Xs, because binding spreads "virtually" (not mutably) via the runtime instantiation of Cells in evaluation, one List at a time...
The API Benefits Tremendously From Being Able To Do Such Lookups! But so does everything else... whether that's resolving references between modules that don't have a specific ordering, constructing code from disparate sources on the fly, or doing String Interpolation.
It does mean you have to be more binding conscious, and learn the new tools. BIND no longer mutates bindings on lists, but returns a new list with a tweaked binding at its "tip". (But note BIND has always done this with WORD!s... giving you a new word with the binding you wanted vs. somehow changing the word you passed in.)
There's still no shortage of things that need to be figured out. But when so many features are now actually working, it's strong evidence for the direction.
Hammering Through FRAME! And PARAMETER!
I struggled for some time solving Critical Design Points of Antiform FRAME!s as Actions. Mumbo-jumbo aside, neat behaviors that were prophesied are finally reality...
Since "actions" are just antiform frames, you can pick elements out of them as if they were objects. And any fields exposed on the interface are PARAMETER!, by definition:
>> get $append
== ~#[frame! "append" [series value part dup line]]]~ ; anti
>> append.dup
== #[parameter! :[any-number? pair!]]
>> append.dup.text
== "Duplicates the insert a specified number of times"
>> append.dup.spec
== [any-number? pair!]
>> append.dup.optional
== ~okay~ ; anti
The system isn't making some artificial object-like interface to what it knows. That object is the in-memory representation of what APPEND is.
It means you have everything in your hand to build your own specializations (and specialization tools)... just copy and adjust. And if you want to run a frame from a WORD!-reference, tweak a byte to make it an antiform:
>> frame: copy get $append
== #[frame! [
series: #[parameter! [~void~ any-series? port! map! object! module! bitset!]]
value: #[parameter! [~void~ element? splice?]]
part: #[parameter! :[any-number? any-series? pair!]]
dup: #[parameter! :[any-number? pair!]]
line: #[parameter! :[]]
]]
>> frame.value: 10
== 10
>> append10: anti frame
== ~#[frame! "append10" [series part dup line]]]~ ; anti
>> append10 [a b c]
== [a b c 10]
MO/RE:PART.(s) {For {Dialecting}}
It took a while to convince myself that the system was better served if {FENCE} joined [BLOCK] and (GROUP) as a fundamental list type. But I was convinced, though I'm still pondering their precise evaluator behavior...
>> fence: first [{a {b} (c d)} {e}]
== {a {b} (c d)}
>> length of fence
== 3
>> second fence
== {b}
(Strings of course had to take another representation, and --{dashed strings}--
have worked out much better than I expected, to where they're superior in very many cases).
I also added a third type to the interstitially-delimited set as well -- ch:a:in joins pa/th and tu.p.le
>> path: first [{a}/b:(c d).e f.g]
== {a}/b:(c d).e
>> length of path
== 2 ; so path elements are [{a} b:(c d).e]
>> chain: second path
== b:(c d).e
>> length of chain
== 2 ; so chain elements are [b (c d).e]
>> tuple: second chain
== (c d).e
They can be interpreted unambiguously due to hierarchy... paths are outermost, tuples are innermost, and you can't construct illegal groupings:
>> join chain! @[a/b c/d]
** Script Error: Value is not a valid &[chain] element: a/b
>> join path! @[a b:c d]
== a/b:c/d ; three element path, not two element chain
(Though this means that FOO:
is now a CHAIN! instead of a fundamental type, the change builds on existing compression so it doesn't cost any more than a WORD!.)
Using The Parts: CHAIN! Refinements, PATH!'d Functions
I don't know what to tell skeptics of the need for the additional parts. If they say they aren't interested in having pieces like (set group):
... uh, ok?
They're cool, though. If var holds a bound word x...
>> (var): 20
== 20
>> x
== 20
>> compose* [(var): 30]
== [x: 30]
But in a previous section I showed how being able to say append.dup
allows a different semantic of picking the PARAMETER! field out of append's FRAME!. If you didn't have generic tuple and only had paths, you couldn't distinguish wanting that vs. invoking append with a refinement.
Now we have three choices:
append.dup
append:dup
append/dup
I believe append:dup
has emerged as a better choice for refinements than slash, since slash serves its strongest role when it points out functions themselves. This means a.b.c/d:e:f
can be read unambiguously... D is a function, selected out of C...while E and F are refinements. And you never have to worry about invoking functions with tuple selections.
In this system you could write /negate instead of plain negate to call it, though you probably wouldn't in most code. But dialect-wise it can help, e.g. with UPARSE's "action combinator":
>> parse [1 2 3] [collect some [
keep /negate integer!
]]
== [-1 -2 -3]
While leading slash kind of pairs a function invocation with what follows it, trailing slash was chosen for suppressing function application, so you (a) know it's a function and (b) know it's not taking arguments:
>> replace [a 1 a <baby>] word?/ <ice> ; word? doesn't take <ice> argument
== [<ice> 1 <ice> <baby>]
This also gave rise to double-slash for the APPLY infix operator:
>> append // [[a b c] <d> :dup 2]
== [a b c <d> <d>]
Even more magic is in order, with the ability to use slashes to construct cascaded functions, one slash per function:
>> my-odd?: not/even?/
== ~#[frame! "my-odd?" [number]]]~ ; anti
>> my-odd? 1020
== ~null~ ; anti
So when I examine the problems which the evaluator needs to solve...and what dialect authors need to solve...I see the same story play out. You need parts, and the biggest trick is being sure those parts are reliable and coherent.
Tons More I Could Discuss...
But that's the "executive summary" of the biggest thought-pieces that stand out to me from 2024.
There's also:
-
Flexible Logic: [true false on off yes no] are just words. This is the culmination of my longstanding belief that Rebol shouldn't have a LOGIC! literal type. Instead, the antiforms of ~null~ and ~okay~ are triaged to and from words on an as-needed basis, and just make that easier and less error-prone.
>> true ** Script Error: true word is not bound to a context >> true? 'true == ~okay~ ; anti >> true? 'potato ** Script Error: true? expects ['true 'false] for its word argument >> 1 = 2 == ~null~ ; anti >> boolean 1 = 2 == false
-
Antiform tags became "Tripwires", an idea which has had a lot of leverage... replacing the mistake-prone idea of "erroring functions" as placeholders.
-
Intrinsics made certain function calls very fast... fast enough that they can be used to do typechecking, instead of limiting typechecking to one of 64-bits in a typeset. So not only are there now more than 64 fundamental types, you can also check arbitrary things (like if a parameter is EVEN?...)
-
The null/void/blank/trash model firmed up a bit more, as I let go of the idea that void had to be represented by emptiness (with single tick mark as its quoted form, and a single tilde as its quasiform). Once I realized the antiform
~void~
was better written out as a word, then antiform BLANK! could take over the role of "nothing" for unsetness, and quasiform blank as the reified role of "trash". -
SIGIL! gave an easier way to talk about "weird words". These SIGIL!s only have an undecorated 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 [...])
-
Definitional RETURN type exposure is function-composition sensitive. I haven't written this up yet, but now even this works. So it can synthesize the HELP from the arguments of the enclosed function but the return of the encloser:
>> appendcount: enclose append/ (func [return: [integer!] f [frame!]] [ eval f return length of f.series ]) >> appendcount [a b c] spread [d e f] == 6 ; length of series after the append is run >> return of appendcount/ == #[parameter! [integer!]]
Where To Now...
Going to try to get back on track, and firm everything up enough so it's solid enough to push to master.
There's some work on the TO to MAKE matrix that I didn't cover here that actually turned out to be a pretty big thorn, and I've been putting off some decisions pertaining to that... because it's frustrating work.
Updates as I have them.