Opportunistic Invisibility

When "Invisibles" were introduced, a function was either always-invisible or always-not-invisible. You couldn't make a function that "sometimes returned an INTEGER!, and sometimes was invisible."

Due to changes in the way the evaluator interacts with invisibles, this doesn't need to be a rule anymore. There could be functions which decide--perhaps based on their arguments--whether to act invisibly or not.

This has actually been requested. Imagine you are dealing with something like DUMP, and you want to call out that you'd like to actually return one of the things you are dumping:

all [
    ...
    x * 10 + 20  ; want to print this expression and see its evaluated result
    ...
]

=>

all [
    ...
    -- (x * 10 + 20)  ; now it's not participating in the ALL anymore
    ...
 ]

@gchiu wanted some way of getting a printout like -- (x * 10 + 20): 40 but also having the value still returned, without needing to name another operator. So maybe you could mark this, e.g. with a GET-GROUP! or GET-WORD! in the DUMP dialect.

Having this particular debug construct behave in that way may not be the best idea in the world.
But we don't need to technically prohibit it.

This could give an interesting nuance to the difference between return and return void. RETURN used alone could just mean literally "return and don't have any value, not even void". It's certainly a weird idea, but it's consistent...and we could make sure you knew what you were doing by requiring some kind of invisibility in the type signature of a function before allowing a 0-arg RETURN. Of course there could be other syntaxes, like return/invisible...which would probably fit practice better by leaving plain RETURN as returning void.

There'd need to be a new syntax for saying you could return invisibles, because return: [] as the indicator wouldn't allow you to specify it along with other types.

Worth Doing?

The main argument for opportunistic invisibles is to keep you from having to create different names for the invisible and visible forms of a function, when there could be a learnable convention for knowing which it is from the arguments alone.

The more fanciful argument is creating strange execution patterns in code golf, where you have some kind of "voting" mechanism e.g. inside an ALL and program behavior emerges from things opting out. This harkens back some to the "null opts out of logical operations" ideas from the early days of null behavior.

Normalizing invisibles to the point of saying that they're what you get when you just do a plain RETURN is a fairly weird idea, but they're a pretty powerful feature that might have even more applications than yet thought of. most free-form programming language ever created is still the goal, so... I think this deserves some consideration.

2 Likes

On the plus side of saying that RETURN with no arguments means "invisible" (distinguished from RETURN VOID to return a void value) is that it facilitates wrapping.

Imagine if MAYBE-INVISIBLE is a function that might return a value, or might be invisible. Then you want to write a wrapper for it:

wrapper: func [x] [
    if x = 10 [return 20]
    return maybe-invisible x
]

In the event that MAYBE-INVISIBLE vaporizes, this will act like you had written just a plain return. So if plain RETURN means invisible, then you get an effective pass-through of the invisibility.

...BUT...On the minus side is that it doesn't play very well with the idea that a variadic RETURN could be used with multiple return values somehow. Imagine if return ret1 ret2 could line up with a callsite that said [x y]: ... where x would get ret1, and y would get ret2.

Multiple Returns Tip the Balance

It was already a bit "out there" to suggest that plain RETURN would do anything other than return a void. Imagining that you can return multiple values variadically...it seems to make sense to say that any of the values you don't provide (including the first) default to void.

So in light of that, how do you say you want to return nothing at all? It's best if it's some option or behavior of RETURN, because return is already definitional (carrying a link to the frame). If you wanted another keyword (like VANISH) you'd be taking up another frame slot for every single function to have that definitional alternative...and it would mean chaining like wrapping would necessarily have to branch to call two different functions.

If it were a refinement, the semantics could be "if I say return a void, then actually act invisibly"... so you could say RETURN/INVISIFY (or whatever) and it would be able to work with multiple return values if the value in the first return slot were void...and it would also work if you provided no arguments.

But that won't work fully generically with a function that could return ANY-VALUE! or NULL -or- be invisible. So how would you chain the MAYBE-INVISIBLE above? :frowning:

One answer here could involve modal-like parameters...so instead of giving RETURN the by product of an evaluation, you gave it something to evaluate. If return @(comment "hi") were passed then it means RETURN has a chance to distinguish vaporization. This may be an interesting side property of modal refinements...to tell you whether something vaporized (so perhaps the value of the modal refinement could be NULL for not modal, and then maybe FALSE for vanished void vs. TRUE for non vanished void?)

In that scheme, returning nothing could be said as return @() ... with the usual escapes of return '@() if you actually wanted to return an @().

That Aside, Opportunistic Invisibility is Almost Implemented

It's tricky to write. But I think of it ultimately as a "simplifying" aspect on the evaluator...as it removes the separate concept of an "invisible function".

There are some things that can't be done if you don't know a-priori that a function is invisible...and have to wait to find out until after it has run that it is. But I think few people would notice the subtlety of what that implies.

This puts some pressure on type specs, because now we have another return state that isn't an ANY-VALUE! besides null. The return: [] is no longer going to work.

What I have right now is that if you want to say that a function is invisible even if it just drops out of the bottom with no RETURN statement, then you say return: <elide> This is like the return: <void> instruction, although I feel like using a name different from a datatype makes more sense... so maybe it should be return: <voided>

Then there's the question of how to put a non-datatype in the spec list for the return. This is something that can only apply to RETURN...which makes it a whole-function property more than a

I've been wondering if ISSUE! is better for these states (#null, #vanish) and using 7 characters or less on 32-bit platforms has a nice savings over <opt> and <invisible>.

As usual, no shortage of things to think about. But invisibility has become a core differentiating feature...it's novel and it's powerful. Embracing it systemically is a good thing.

1 Like

Behold, the first opportunistic invisible:

>> vanish-if-odd: func [return: [<invisible> integer!] x] [
       if even? x [return x]
       return @()
   ]

>> <test> vanish-if-odd 2
== 2

>> <test> vanish-if-odd 1
== <test>

Which was followed immediately by the first chaining opportunistic invisible:

>> vanish-if-even: func [return: [<invisible> integer!] y] [
       return @(vanish-if-odd y + 1)
   ]

>> <test> vanish-if-even 2
== <test>

>> <test> vanish-if-even 1
== 2

Remaining questions:

  • Should it be disabled by default if you don't say anything on the RETURN? I thought it would have to be disabled for sure when you could get it with plain RETURN. But now you have to use the @(...) form of RETURN to get it, and the only reason you would use that form is if you wanted to pass through invisible results. So, no. Enable it by default.

  • When you do have a type spec, what do you put in it to say invisibility is okay? Above I put the tag <invisible>. But I've mentioned I'm not 100% pleased with the existing <opt> tag for NULL. Some days I feel like #null would be less obtuse. That could be complemented by #vanish which is short enough to fit in a cell as a constant.

Like I say...implementation-wise, it's a bit of a mixed bag in terms of making some things simpler and some things more complex. But as a feature, I think it's something that will open up space for exploration...especially in debug constructs. Should be very fun for code golf. :golf:

2 Likes