It's Time To Revisit This
At one point, ACTION! and FRAME! were two different types, whose Cells pointed to two different data structures.
An ACTION! pointed to a DispatchDetails*
(or Details*
for short). A Details contained the implementation information for the action... so if it were something like a FUNC, then it would hold the BLOCK! of the body for that specific function. It also held a pointer to a C function called a Dispatcher*
which would know how to interpret those details (e.g. the Func_Dispatcher() would know to run the code in the BLOCK! in the Details array). Invoking a function could wind up running a chain reaction of Details, that had been composed together to run in the same memory space.
A FRAME! pointed to a VarList*
... which is the same kind of thing that an OBJECT! points to: a list of keys (symbols) and then Cells for each key's value. However, the cells could either specify an unfulfilled argument (antiform PARAMETER!) or a specialized value...which includes locals. While this VarList pointed to the specific Details it was for, the FRAME! Cell itself also held another Details* (as the "Phase"). Then, whether you could see the locals or not (or which args and locals) depended on the VarList*
of the Phase held in the cell... which is a nuance that made frames quite a bit trickier than objects.
Could FRAME! be Just a (Details*, VarList*) Pairing?
When you are going to add new execution information... you necessarily must create a new Details. There simply isn't anywhere to put code in a VarList. However, you can reuse a VarList with that new Details.
e.g. if you were to ADAPT an ACTION! then you'd produce a new Details*
which would point to the same VarList*
as the thing you were adapting... but just give it a new Adapter_Dispatcher()
to with some preamble code in the Phase's array to run. (It also needs to store the function it is adapting, so it can pass control to it when it's done running the preamble.)
When all ACTION! needed a unique Details, things like SPECIALIZE would create a new Details too, but just a dummy one...since all the information for the specialization exists in a new VarList*. But ACTION!s had to have a Details, so that's what it did.
A tempting thought is that a FRAME! could just be a Details* and a VarList* paired together in a Cell. If this were the case, you could create a specialization without making a new Details... which has been a sought-after optimization for some time.
There are a few casualties of such a design. One is that this breaks the notion of a single pointer being an action's "identity"... you've just got a list of parameters and variables paired up with a compatible implementation...and these are being mixed and matched freely by function composition tools. The HIJACK capability was only designed for Details pointers, and this would mean you could not hijack a specialization (at least without hijacking all the other specializations of the same function--in practice this is every use case of HIJACK that has come up, but there's nothing in theory saying you wouldn't implement a "unique" service entry point by way of specialization and want to HIJACK that... I can easily imagine it happening, it just hasn't yet.)
Another casualty would be the storing of a Symbol* inside FRAME! values, which is reliant on a VarList* storing its one-and-only Details* inside it. If Phases can be mixed with arbitrary Varlists in a FRAME! cell, then there are no free bits for this feature.
Elegant Design > Fringe Features?
It's likely that if I were looking at this from scratch--before HIJACK or symbol caching--I would think that the FRAME! Cell => (VarList* + Details*)
made the most sense.
It feels like it decouples things cleanly. Why would you need to create a new Details*
if you're just specializing out values? If you've made a new VarList*
with the updated values, couldn't it be paired up with the old Phase?
It may still be possible to implement HIJACK in such a world. You could ask the Details* in a FRAME! Cell "what's your VarList*", and if the answer was the same as the VarList* in the Cell then you'd know the Phase was created after the VarList, hence the Details must be the "identity"...so you hijack that. Otherwise, the VarList* must be the identity...so you'd hijack it ( hand waving as to how this would work)
Additionally, it may be possible to sneak a Symbol* into FRAME! Cells if they aren't using their "Coupling". (e.g. if they're not a METHOD, or RETURN/CONTINUE/YIELD/BREAK/etc.) A Symbol* in the coupling slot could just indicate it's uncoupled. That's...fairly random. Though to be honest, the feature turned out to be somewhat hard to wield anyway, it was never really clear which assignments should rename the function.
There are a few more glitches, though.
Devil's In The Details Irreducibility Of Typechecking
Before you run a Details, the expectation is that all the unfulfilled slots in a VarList will be filled... with type-correct values. Calling any function with incorrectly typed cells is bad, but native code actually expects the bit patterns in Cells to be specific to what it asked for...and will crash if it's wrong.
So if a FRAME! is a mix and match of a VarList*
and a Details*
, when does the VarList*
get checked?
There are some weird cases to think about like AUGMENT, which adds to a function's frame. It only makes an expanded VarList with new keys/cells... there's no implementation code. So it should be able to reuse whatever Details* was in the FRAME! Cell it augments. But this means the type checking and argument gathering has to use the VarList* in the cell, not the VarList* that the Details was coupled with.
But wait: the VarList* in the Cell is an arbitrary coupling, with arbitrary tweaked values. You may have overwritten an argument with a specialized value, that needs checking. So you can't use that list instead of the VarList* the Phase was coupled with.
Exploring this problem further: if PARAMETER! antiforms are indicative of needing to gather that argument, what happens if you tweak it?
>> ap-int: copy meta:lite append/
>> ap-int.value: anti make parameter! [integer!] ; or whatever syntax
== ~#[parameter! [integer!]]~ ; anti
>> /ap-int: anti ap-int
== ~#[frame! ...]~ ; anti
Does this mean you've just created a variant of the APPEND function that only takes integers for the value to append? Well, not so fast. APPEND is native code, what would happen if you added a type check which would allow types to pass that weren't legal for APPEND? You still have to run APPEND's type check.
There's no way to check programmatically if one type constraint is a subset of another. So if you're allowed to re-type parameters, they have to go through both checks...somehow.
Not So Simple...
This makes it seem like AUGMENT needs to pay for a trivial Details, in order to get its VarList into a Phase position. (A trivial Details can be 8 platform-pointers in size, it's not a huge deal, but annoying.)
And it makes it seem like you can't update FRAME! slots that are PARAMETER! antiforms to be new parameter antiforms, without some yet-to-be-designed mechanic... because there's only one type check when slots are filled, and that is that.
Furthermore... if there's no moment of type checking and a specialization is just a Cell that pairs together a VarList* and a Details*, you'd have to type check a specialization every time it's called.
This shows a feature of the previous creation of a "Specialization Details" when making an ACTION!, that defined a moment where you could check all the VarList slots and make that the Details's new VarList.
So FRAME!/ACTION! Unification Was A Mistake?
Well, not so fast. I'm just mapping out the territory. (There's actually more to consider when you weigh in things like partial specializations which specify the use of a refinement, but not the specific value for that refinement, which has to encode ordering and other issues.)
It may be that when you invoke a FRAME! as if it were an action, then it remembers if the type check passed when the arguments were filled in. If it does pass for everything, then it knows that it must have passed the specialized portions, and then marks it ok to not have to test the specialized portions the next time. This means that if you try and call a "fresh" frame function with bad arguments, you won't validate it, but since the call raised an error that extra typechecking is probably not a big issue, and it will just try again next time.
Re-typing a PARAMETER! appears to be more involved than first thought. Just filling in a FRAME! slot isn't going to do it, you'd need another operation.
Anyway, this has been driving me crazy the last few days so I had to write something down about it. I definitely underestimated just how many issues were folded in with the "hardening" process that creating a Details brought about...and how many features were tied into Details* being a unique "action identity". I'm going to have to experiment a bit before I can resolve what's best here.