Still paying off technical debt, here...this frame stuff has been agonizing.
As part of trying out this "two unspecialized forms" concept, I started tightening the screws so I can better reason about the invariants. It quickly exposed something fundamental, that I might classify as "genuinely interesting".
Consider the native for testing if something is an INTEGER?. Let's say it's able to accept ANY-VALUE?, and it should not return true for PARAMETER! antiforms:
/integer?: native [return: [logic?] value [any-value?]]
We don't want to be in a situation where a function like this needs to take its argument as ^META, because that's a trap we don't want to fall in... where every parameter has to be meta. You don't want the edge case to spread like that.
But now we have to ask: what if you have an antiform PARAMETER!, and you ask INTEGER? of it.
The options are limited to basically 2 choices:
-
Antiform PARAMETER! is removed from the ANY-VALUE? class. That means testing INTEGER? on an antiform parameter would trigger an abrupt failure.
-
Antiform PARAMETER! is made legal in EVAL frame contexts to mean "itself" (while its meaning in RUN frame contexts is "unspecialized").
When you put it that way, I get bad vibes from (1).
Though (2) has consequences that I've explained. Any function that takes antiform PARAMETER! which doesn't take it ^META will not be able to create partial specializations that fix arguments as antiform parameters, while still gathering other parameters. You will only be able to use antiform parameters in full specializations.
But I realized you -could- create partial specializations on functions taking antiform parameters non-META. You'd just have to get creative: make a variant with the parameter adjusted to be ^META and then, use an ENCLOSE...after all the arguments are fulfilled, unmeta it before passing through to the original function.
Having a way to do it is good enough for me. I do not think that partial specializations to antiform parameters is going to be something anyone does frequently (or ever, probably, outside of the test files I'm going to write that prove it can be done.)
Down To Just One ^META Exception: NOTHING (~
)
A "genuinely interesting" aspect of this is that it brings a renewed motivation to the existence of the unset state. It has been challenged before:
Why Have an "Unset State" in Rebol-like Languages?
I actually was reflecting on the question of the necessity of "nothing" once I realized that protecting-you-from-typos is no longer one of the reasons, due to binding becoming "strict". So I had a panicky moment where I wondered if ~null~
and ~
antiforms should be merged, after all.
Almost certainly not... but with PARAMETER! antiforms now in frames as "themselves", something has to give. There's a strong incentive to pare that back to as few states as possible... and hence, just one thing: NOTHING.
Note it's thus subject to the limitations of (1) above, and hence you can't call INTEGER? on NOTHING.
So NOTHING becomes (as it was) the one stable antiform state that a variable can hold, which cannot be accepted by functions that don't take ^META parameters.
If we didn't do something special, the error you'd get would look like:
>> integer? ~
** Error: INTEGER?'s VALUE argument is unspecified (~ antiform)
Maybe that's good enough, though the argument-gathering machinery could pre-empt the FRAME! typechecking layer with a clearer message:
>> integer? ~
** Error: INTEGER?'s VALUE argument can't be NOTHING (~ antiform)
That may be seem like splitting hairs, but I don't think it is.
Casualty: Unblocks EVAL of Unspecialized FRAME!s
So with antiform parameters being treated as-is under EVAL, then when you don't use MAKE FRAME! and get the slots filled with nothing, you get the slots filled with antiform parameters. And that means this would happen:
>> f: copy unrun parameter?/
== #[frame! [value: ~#[parameter! [any-value?]]~]
>> eval f
== ~okay~ ; anti
I can live with this. COPY on an action's FRAME! is something you should do only if you're building something you mean to run as an action, and if you EVAL it instead, that's kind of your fault.
Maybe there could be some kind of prevention of this, but I'd be loathe to see the FRAME! type bifurcate into "EVAL-able frames" and "RUN-able frames".
Anyway, this is far less a concern with MAKE FRAME! unsetting the slots, than it was when those were antiform parameters.