Imagine you write something like:
try-digit-to-bool: lambda [i [integer!]] [
switch i [
0 ["false"]
1 ["true"]
3 [print "DEBUG: got 3" null]
]
]
I feel it's a bit of a thorn that the following can happen:
>> null? try-digit-to-bool 1 ; switch branch ran
== \~null~\ ; antiform (keyword!)
>> null? try-digit-to-bool 2 ; no switch branch ran
== \~okay~\ ; antiform (keyword!)
>> null? try-digit-to-bool 3 ; switch ran but made null
DEBUG: got 3
== \~okay~\ ; antiform (keyword!)
>> null then [print "THEN"] else [print "null on left"]
null on left
>> try-digit-to-bool-1 then [print "THEN"] else [print "null on left"]
THEN
>> try-digit-to-bool-2 then [print "THEN"] else [print "null on left"]
null on left
>> try-digit-to-bool-3 then [print "THEN"] else [print "null on left"]
DEBUG: got 3
THEN
This Would Not Happen With A Proper Typespec
You have a problem that a "heavy null" (a null in a PACK! antiform) acts like a null most of the time, but not when it's on the left hand side of a THEN or ELSE.
TRY-DIGIT-TO-BOOL is the kind of function which doesn't seem to be the sort of thing that intends to return a heavy null. You can fix this problem with a return spec (since it's a lambda, you use []: and not return: because there is no return word in the frame):
try-digit-to-bool: lambda [
[]: [<null> text!] ; I'm thinking <null> is a nice synonym for NULL?
i [integer!]
][
switch i [
0 ["false"]
1 ["true"]
3 [print "DEBUG: got 3" null]
]
]
Since PACK! is not in the typespec, this will decay.
>> try-digit-to-bool-3 then [print "THEN"] else [print "null on left"]
null on left
But... Should Decaying Be The Default?
This is a little bit tough to reason about.
If you think about multi-return as a feature of functions, then it does seem a bit irresponsible to say that you become a multi-returning-function just because you used a call to another one in your implementation.
If you think about PACK! as being a "legitimate" value and the desire for lambda [...] [other ...] to pass through whatever "other" makes if you don't say any different, you might have another impression.
Since VOID is a pack, this means you wouldn't be able to return voids without a typespec. (Functions generally should not return void anyway.)
Decaying PACK! If No Typespec Makes Sense
It doesn't have to apply to VOID! or ERROR! (VOID! is already covered by VOIDABLE so we don't worry about "unexpected" callsite problems)
The issue is just that I think PACK! is special--straddling the line between multi-return feature and an "ordinary" value. When you cross the line into calling a function you don't really want to propagate the multi-return-ness of that function on accident.
This might annoy people who write a one-off function and say return pack [10 20] and they wonder "why did I just get 10"? But if you said x: pack [10 20] you might also wonder why X just got 10 until you realize the whole design hinges on that, and you need to say ^x: pack [10 20] if you want the special interpretation.
We might argue you have to say ^return: [...] or ^[]: [...] and it decays otherwise despite your type spec. This would mean returning unstable antiforms would always require it.
That might be good documentation. You'd need it for a function argument that took a PACK!, why not for a return that gives one?
I'm not sure about the need for the ^ there. That would suggest you couldn't write:
comment: ghostable lambda ['ignored [block! text!]] []
Returning an unstable antiform would require an unstable antiform result typespec, so:
comment: ghostable lambda [
^[]: [ghost!]
'ignored [block! text!]
][
]
Part of me likes this formality and how well it parallels the behavior of ^META arguments, that you always are getting decay unless you explicitly acknowledge that you aren't getting it. Then this lines up with the default behavior of decay.
Another part of me thinks there's no reason for this rigid level of symmetry, and return values have a good excuse for being special.
Either way, PACK!s should decay by default if no type spec present, let's start there.
Addendum: What about DOES?
DOES has nowhere to put a type spec.
That dovetails with a concept that you pretty much have to have the semantic be []: [any-value?] (or ^[]: [any-value?], if the caret becomes a thing, which I am skeptical it will)