When you make a new FRAME! for a function, it has all of its fields unset.
foo: function [x [integer!] /y [integer!] /z] [...] >> f: make frame! :foo == make frame! [ x: ' y: ' z: ' ]
(Note: While the molding communicating that those fields are null may be imperfect, it's at least an improvement on history. The lone apostrophe means that if the frame were evaluated, you'd get a null field. See Generic Quoting Makes Headway on MAKE OBJECT! for more details on how problematic history was with this.)
If you were to try and do f on that frame without setting x to something, you would get an error. You would also get an error if you said f/x: "not an integer" and tried do f.
A nice property of the unused-refinements-are-always-null is that you can just leave y and z alone. If you DO F it will just mean they're taken as unused. That's pretty elegant...
...but what if you want to SPECIALIZE and create a variant of FOO that doesn't offer /Y or /Z on its interface, but still wants them to be unspecified?
Previously this was accomplished by setting the refinement to a BLANK!. But the price of the elegance of unused-refinements-are-always-null is that it runs up against unspecialized-values-are-null. Can't mean both.
This actually isn't new--the problem existed before with
<opt> arguments. You couldn't make a specialization of REPLACE that would use NULL as the replacement pattern (a non-refinement argument). But it hadn't come up. However, removing refinements and keeping them "turned off" comes up a lot.
Custom Escape Value?
A first thought I had was we could do something along the lines of just telling SPECIALIZE a value you want to use as the escape:
foo-no-y: specialize/null 'foo [y: <nullescape>] <nullescape>
With skippable parameters, we could even do this in the style of labeled compose:
foo-no-y: specialize <nullescape> 'foo [y: <nullescape>]
It sounds plausible, until you think about scenarios where some of your data is being passed in from outside:
make-specialfoo: function [data-from-elsewhere] [ return specialize <nullescape> 'foo [ x: data-from-elsewhere y: <nullescape> ] ]
What if the passed in
data-from-elsewhere just coincidentally used the same magic escape value you did? I'm not saying such a construct shouldn't or couldn't exist and be useful somewhere. But it's a pretty big weakness for "the" SPECIALIZE to have.
Assume only user-assigned nulls are specialized
The current implementation of SPECIALIZE doesn't keep track of whether you assigned a field or not.
It just starts with frame fields as null, runs your code bound into the frame, and when the code is done it assumes anything that's still null is unspecialized. So these act the same:
specialize 'foo [x: 10 y: null] specialize 'foo [x: 10]
Technically speaking, it's actually possible for the system to write a bit on the value cell in the frame... and notice whether that bit is still set at the end. So it could tell a "stale" null from a user-written null.
Leveraging such internals makes me a bit uncomfortable, in the sense of being something the user can't do. I'd like to see basic mechanics exported so that users could write their own variations of SPECIALIZE.
One alternative could be that all the unspecialized fields could be set to VOID! instead of NULL by the specialization process...so it would be a check for VOID! and not null. That's something users could do with their own variations. Though saying that people can't specialize functions to take void is another version of the same limitation that someone would presumably complain about someday--and have a legitimate reason for doing so.
Make removing args a separate refinement
In the lazy/obvious department, just have a list of the arguments you want to remove, passed as a block or something:
specialize/remove 'foo [x: 10] [y]
Not very imaginative.
Likely Winner: Expose A New Meta Bit
A more imaginative concept might actually let the user programmatically request the field be removed from the frame's interface. It might even reuse the mechanics for PROTECT/HIDE:
specialize 'foo [ x: 10 protect/hide 'y: ]
A backwards-quoting operator might even do that with a nicer syntax...perhaps a function that is bound to work in concert with SPECIALIZE (the way KEEP works with COLLECT):
specialize 'foo [ x: 10 y: hidden ; or SPECIALIZED-OUT/etc...it would quote left and fiddle bit ]
I think I like this direction, even if it introduces a new bit to mess with. It may be able to just build on the PROTECT/HIDE feature, and if so it might bring more justification to its existence. Or maybe just the plain old PROTECT bit...if you're saying the field is protected, it's NULL and can't be changed, what else could you mean by that than "it's in its final form, so remove it from the interface"?
The idea is that I want to be able to flip back and forth between MAKE ACTION! from a FRAME! and MAKE FRAME! from an ACTION!. This is the long game of writing your own specialization operators. It can't be done completely in-band with values and nulls...since they represent valid states for a function's arguments. So as long as the function call itself can't read this hidden bit controlling frame creation and derive meaning from it, it should work.