So I want to talk briefly about this choice for the "end signaling antiform", and how that ties into "default values and make frame!"
Right now, when you do make frame!
you get a copy of a frame with all the unspecialized slots (e.g. PARAMETER!) initialized to trash (~
antiforms):
>> frame: make frame! either/
== #[frame! [
condition: ~
okay-branch: ~
null-branch: ~
]]
(Quick reminder that although what's shown there are quasiform blanks, the convention is that you interpret foo: xxx
as meaning the FOO field actually holds what xxx evaluates to. So condition: ~
means that the CONDITION field holds the evaluative product of the ~
quasiform, e.g. the ~
antiform... an "unset!" if you prefer to think of it that way.)
The question on the table relates to whether normal evaluative parameters are able to receive "trash" or not. So let's imagine I did this:
>> frame.okay-branch: [print "truthy"]
>> frame.null-branch: [print "falsey"]
>> eval frame
; ...is this an error? prints truthy? prints falsey?
In the current paradigm, it's an error:
>> eval frame
** Script Error: either has condition unspecified (~ antiform)
So the rule is that "trash" is the one stable antiform which you can't accept as a normal argument.
If this is truly the case, it would seem like the obvious candidate for signaling an <end>
, as the state is already reserved in all cases...
...or is <END>
a different intent from Unspecified
?
More accurately, the question is: "Even if there's a difference in intent, is it worth it for overall system complexity to have a separate antiform and handling?"
Because clearly when an end of evaluator input is reached, all subsequent argument gathering is also at the end. Whereas unspecified parameters in a frame can occur in any order.
(e.g. I didn't assign the condition field of the EITHER frame above, but then filled in both branches. That's impossible in left-to-right fulfillment to mean "the end was reached at the condition")
Hmmm.
Case Study: MAKE FRAME! on Variadic Input
There's an experimental variation of SWITCH that allows you to use partial expressions:
>> switch2 1020 [match integer! => [#i], match tag! => [#t]]
== #i
So here you see an arity-2 function (MATCH) receiving only one argument. And the SWITCH2 fulfills the missing argument:
How it works is that the evaluator is called on the incomplete expression, and then writes an ~end~
antiform into any slots that are at the end:
>> frame: make frame! [match integer!]
== #[frame! [
test: &[integer!]
value: ~end~
]]
Today that's conflated: you'd get the same thing if you wrote:
>> frame: make frame! [match integer! ~end~]
== #[frame! [
test: &[integer!]
value: ~end~
]]
So it seems like an improvement if the situation was:
>> frame: make frame! [match integer!]
== #[frame! [
test: &[integer!]
value: ~
]]
>> frame: make frame! [match integer! ~]
** Script Error: match can't take (~ antiform) at callsite
The deal would just be that you can't take ~ antiforms at the callsite unless the parameter is ^META (which means you'd receive the parameter as a ~ quasiform, still leaving the ~ antiform free for unspecified-ness).
Could Typechecking "Trash" Replace The <end>
Flag?
Imagine if a parameter typechecks against the trash antiform, then we assume it is "unspecifyable". So not assigning it in a frame... or reaching the end of an input evaluation... however you do it, it's all right to receive as a trash antiform.
But if it's driven by typechecking alone, then it's actually the ^META parameters that get mucked up. Because if they say they typecheck the trash antiform, they (historically) mean they will receive evaluative trash arguments as meta-trash.
So one of these things would have to be true in such a world:
-
^META parameters can't be endable/unspecifyable
-
^META parameters which accept evaluative trash conflate it with unspecifyability
-
^META parameters can't accept evaluative trash, and typechecking it means unspecifyable
[1] is right out. ^META is an expansive parameter convention when you've hit the limits of what a normal parameter can do. It needs to be strictly more powerful.
[2] and [3] seem to involve unpleasant tradeoffs.
Consider (set var ~)
I think that needs to work, because (var): ~
works And SET has to take its value argument as ^META (for other reasons, like skipping the assignment and propagating definitonal errors). But this shouldn't imply that (set var)
works.
In conclusion, unspecifyability needs to be a separate flag from typechecking.
What To Call Unspecifyability? <end>
Feels Wrong Now
I've been calling refinements "optional" parameters. e.g. ":DUP
is an optional argument to APPEND
"
>> append.dup.optional
== ~okay~ ; anti
But maybe I should stick with the name "refinement". And this idea of being willing to be left unspecified is <opt>
... true optionality. Like, you can literally omit it from the callsite.
It creates a little bit of a problem, because not specifying a refinement in a MAKE FRAME! situation--leaving it as trash--has meant that refinement argument will be set to NULL when the function runs.
But an alternative perspective on MAKE FRAME! would remove the unused refinements:
>> make frame! append/
== #[frame! [
series: ~
value: ~
]]
I proposed what has traditionally been thought of as MAKE FRAME! be some other operator. But maybe that operator doesn't leave things as antiform trash, yet rather sets them to null?
>> XXX append/
== #[frame! [
series: ~null~
value: ~null~
part: ~null~
dup: ~null~
line: ~null~
]]
This could get rid of the concept that typechecking ever morphs ~
antiforms into anything else. And FWIW, could mean that there could be such a thing as an optional refinement--not that I can think of any actual applications of such a thing.
Devil's in the Details, but...
Regardless of what happens, I do think I like moving away from "endability" to "unspecifyability".
Seems fairly elegant. It's also in line with Carl's "UNSET! is not first class" proclamation:
It's important to understand the unset! datatype; otherwise, we run the risk of assuming that it is first class (assignable, passable, returnable) when it's really not intended for that kind of usage!
...although assignment is one of those "meta" circumstances where it's better to allow than disallow. While comparison and such don't count, though you can "meta-compare" an unset.
>> if ^(print "hi") = '~ [print "meta-comparison works!"]
hi
meta-comparison works!