@rgchris has convinced me, and I am now committed to the idea that "VOID" is not a datatype, but the characterization of what were previously called "invisible" functions.

There is a nice sense in which this lines up with traditional "void" functions in C (and related languages). Because absolutely no value--not even some dummy or placeholder--is returned. It means the libRebol C API function signature void rebElide(...) is consistent with the ELIDE construct signature...they both specify their voidness in their return spec.

But I've written up in a separate post why I don't believe things should too casually be void functions:

The go-to for creating a function without a meaningful return result should be a reified meaningless result...because full-on vaporization is a contract which is difficult to turn back, and is not easy to sense from a callsite (e.g. inside an ALL [])

Relying on a generic tool like ELIDE gives you clarity at callsites of what you're doing, and means you don't have to work "I elide my result" into the name of your function somehow. Returning a meaningless value also permits interop with ELSE and THEN if you decide you want NULL-reactivity.

Maybe PRINT is one of those things that should be an exception (along with assert and -- and breakpoint)...but it would be a rare case.

What Should We Call The Meaningless Result?

2022 UPDATE: I believe we now know that the best thing to use for this is "isotopic blank", e.g. the isotopic form of ~.

I've settled on calling this "NONE".... though behavior-wise it has more in common with what Rebol2 called "unset".

What it has in common with historical Rebol's NONE is that it is a reified concept of nothing. What it does not have in common is that it is neither true nor false, and errors in conditional all the other isotopes.

Retraining Usage Of Arity-0 RETURN

Though it made me nervous at first, I've become very comfortable with func [] [return] being invisible. It makes complete sense with being a "void function".

If you provide a return specification, e.g. func [return: [integer!]] [...] you will be protected from accidentally returning a void with a naked return, just as you would with any other return type.

I'm also happy with the way piping works:

  • lambda [x] [comment x] will act just like COMMENT

    • If we said that lambda [x] [] returned NULL that wouldn't work, because we'd be pre-seeding with a NULL that the comment would have to leave behind.
  • func [x] [return comment x] will act just like COMMENT

So For Non-Invisibility, Always Pass An Argument

[return ~] is little bit ugly, but not unbearably so. That will evaluate the QUASI!-BLANK! to produce an isotopic blank, e.g. a none.

I'd been wanting to avoid functions that returned isotopes to avoid the sorta confusion situation of action! = type of :none ... but if making it an action is what we have to do in order to get the syntax of return none then that is what it is.

Sidenote: Inheriting Return Types

I'm talking about finessing the question of how to do a chained RETURN on a function that you don't know if it's going to vaporize completely or return a value. That's something that bothers me about C, because it doesn't let you write chaining void returns:

void my_function() {
    return some_void_function(...);  /* this is not legal! */

That annoys me, because it makes it hard to write generic code that doesn't want the bad properties of a macro (repeating evaluation of arguments if used multiple times, etc.)...but throws a wrench in being able to abstract across return values.

Yet then there's that other question of how to label your return type. In Ren-C we have the option of just not putting a type signature on it, but what you often want is the signature of what you're wrapping. I guess we could do this via COMPOSE on the spec:

my-function: func compose [
    return: (return-of :some-other-function)
    return some-other-function ...

Anyway, I just wanted to mention that we're not yet at utopia in this medium, even if we're pretty much nailing the chaining part itself!

I'd still lean toward my own sense of what I'd consider (or feel) my primary definitions of NULL and VOID—where NULL is the product of nothing and VOID is the realm of emptiness—a place with no beginning or end, or indeed no definition (void as in voided checks is not the first meaning that comes to mind).

If I were to expand on that, I'd say that NULL is negative nothingness and BLANK is positive nothingness where we must affirm across the wire that nothingness is the intent.

If I were to indulge a little further and incorporate bad words, then VOID becomes more like outer space and the various defined bad words are stars within distant constellations.

1 Like

I think this is why the idea of VOID as replacing UNSET was key—there's an infinite array of words out there and until they are defined, they are just points in an endless vacuous universe. Language can be fluid and subjective, this is where my mind went.

1 Like

Like I said...I'm convinced!