FRAME! / ACTION! Duality Examined

It's been harrowing :persevere: but I've been slowly paying off the technical debt of the FRAME! and ACTION! unification.

I've had some success with the lingering issue is how to deal with the contradictory desires to expose vs. not expose a frame's specialized slots. You clearly want them exposed while you're building the frame (otherwise how could you have assigned them?)

Above I suggested that you'd see the slots when dealing with a non-antiform frame, but you could view it through the lens of the antiform state and not see them.

But I realized that is contentious with things like FOR-EACH interpreting antiform frames as generators.

>> /g: generator [yield 1 yield 2 yield 3]

>> for-each 'item g/ [print ["Num:" item]]
Num: 1
Num: 2
Num: 3

So if you wrote for-each [key val] g/ [...] it wouldn't be extracting the parameters of the function interface, but invoking the function to supply the values.

Better Idea: Utilize "Frame Lensing"

I realized that this would be a perfect application of a feature that already existed (in a form)... which is the ability of FRAME! Cell instances to store an additional pointer to a ParamList "Lens" to use for describing what portion of the interface should be visible:

Understanding FRAME! "Lensing"

A reason I didn't think about this at first was that this was originally called "Frame Phasing" and it pertained to the "currently executing phase". So this field was only available for frames that were in flight in the evaluator...not the kind of FRAME! you would get back from MAKE FRAME! or as a parameter to your shim in ENCLOSE. But after a bunch of switcheroos of naming and concepts, the field is available in non-running frames.

So when MAKE FRAME! happens, it gives back a cell with a lens on it that says to show all the fields on the interface at that moment. Even as you fill in slot with a specialized value, you'll still see it--because it's heding the lens and not the specialization status.

Then transitioning to an antiform gives back a cell with no lens. It's cheap to null out the field, and it makes sense...because the same slot where the lens is held is where you can put a cache of the WORD! symbol a function is extracted from (this is helpful in APPLY scenarios). So when you don't have a lens because a Symbol pointer is there, it doesn't go by the lens...it goes by whether the cells hold specialized values or not.

This works quite well. It does mean that when you say make frame! append/ there's a lens on that frame and hence no room for a symbol. So when you say eval f or run f how do we know to put "append" in the name of any errors? Well that's done by a trick that puts the symbol in a place that's specific to the frame and shared by all references. It can be overridden if you turn the frame into an antiform and have the Cell slot, but if you don't then all frame references will see the same name and it can't be updated.

I should probably write the code in such a way that it doesn't presume lensed cells don't have room for symbols, in case there were some version of the system with larger cells. But another part of me thinks "no, there never will be, this is the end of this design". :slight_smile:

Anyway...I think this is resolving about as well as it can. The biggest issue remaining is when and if the frame is automatically locked against modification. I think that just EVAL'ing it should not lock, because you might want to make a frame and invoke it multiple times with some tweaking each time. This might be a case for just RUN'ing it not doing that either, but then you start having questions about infix. How about when you create a derivation? If you're allowed to change values after specialization then the specialization might have too many or too few parameters for the function as it is. But I'm keeping an open mind on all this for the moment, to see what use cases might arise and just how bad the inconsistencies are in practice.