The Naming of NULL vs. The "Meaningless" Value

Note: When this thread was initiated circa 2020, there was a VOID! datatype that was essentially equivalent to a Rebol2 UNSET! under a different name. It could be put in blocks, but variables holding the value would generate errors on WORD! access. NULL was the only "non-valued" state that could not be put in blocks...a situation that was revolutionized with the rise of generalized isotopes.


the difference between null and void is that null is a non-existent or empty value or set of values while void is an empty space — WikiDiff

Looking at the definitions in the link above, it feels at first read that our NULL and VOID are backwards, that NULL is the tangible placeholder that can appear in a block and VOID is the vacuum.

I think nailing down the definitions of NULL and VOID outside of their current application or behaviour—even simply within English—would be instructive. I still feel unsettled with the way both work in a way I can't put my finger on even if I agree from other posts there is a need for both.

Either way, I feel do [] should vaporize:

reduce [do []] => []
1 Like

My alternative conception was that stamping something with VOID on it is pretty well understood as a way of saying "alert, this thing is tainted, you might keep it for your records of what happened but don't try using it".

But a voided thing would still be a thing, unlike NULL as a complete absence of value.

Maybe the best way to attack the problem is to play out the scenarios in your head:

Q: "What is the value held by variables to indicate they haven't been assigned?"
A: "They contain the state known as <YOUR NAME HERE THAT'S NOT VOID>."

Q: "What does the PRINT function return?  Does it return the string it printed?"
A: "No, that costs too much in the average case that doesn't use the generated
   output.  It just returns a <YOUR NAME HERE THAT'S NOT VOID>...the same
   thing that unset variables hold, so that you'll notice getting an error if you
   try to use the value.  Unless it outputs nothing (hence no newline), in which
   case it returns a NULL...so you can react to that situation with ELSE or THEN
   or whatever."

While VOID might be rethought, NULL is non-negotiable

The parity with C's NULL (and all the languages that also embrace that meaning) is a critical element, and ticks all the boxes:

  • NULL is a 0 pointer value
  • It acts conditionally false
  • It's unable to lead you to data to operate on (e.g. you don't have a "thing" you can set a NEW-LINE on, there's no thing).
  • If a function returns NULL you are not stuck with a handle to an object that you have to free.

Could VOID Be The Result of Invisible Functions?

I could see the case that "VOID" wouldn't refer to a datatype at all, but the return behavior of functions like COMMENT and ELIDE. This interpretation would be really nothing... not a NULL, not an ornery value, but full-on vaporization. You might still phrase that as "The comment function returns void", or "has a void return", or something of this sort...as in C, when in fact returns don't even come into play.

That would line up to having parity with C as far as the interface of "void" goes:

void rebElide(...);  // this is the interface for rebElide, it "returns" void
REBVAL *rebValue(...);  // this is when you want a result back to C

rebElide("print {rebElide means you avoid an API handle to a bad-word!}");

rebValue("print {if you wrote this, you'd leak the returned handle...}");

So we'd be saying inside the language, ELIDE and COMMENT are spec'd to [return: <void>]

If shifting the meaning of VOID like this helps with your interpretation as "less than nothing" (as opposed to "a thing that has been marked invalid") then I'm game for that change. There seems to be a strong enough basis for it.

...But we'd Still Need a Name for "UNSET!"

Taking void for the phenomenon of vaporization would solve one naming problem, but we're still stuck finding a name for "what unset variables hold and that things like PRINT return."

I've explained at length why I don't like UNSET! as that name... because variables are unset, not values. POISON! or TRASH! or other things are awkward and I think maybe too pejorative...it indicates that there's some kind of problem, when there's only a problem if you try to use it in certain ways.

UPDATE: 2024: After trying TRASH for a while, the name NOTHING was ultimately decided to be used.

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).

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.

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 isotopic words are stars within distant constellations.

I get that NULL tracks to other language uses, perhaps my having no background in those languages is why I find it jarring (it's not inconceivable that they all use the word incorrectly :stuck_out_tongue:). I'm use to NONE as being control flow currency so the idea of NULL now having that role isn't that hard to grasp, just the other qualities that don't line up.

You're right in that I need to grapple with code, I need to review the test suite for the R3C branch where other changes aren't as big in comparison to NULL/BLANK/VOID. I should likely stop commenting on this issue until I've done that.

1 Like

Where things stand now hopefully performs to (or exceeds) your expectations!!

(Note: UPDATED August 2024)

VOID is the antiform of the WORD! "void".

It vanishes in REDUCE and acts as a no-op for things like APPEND. It is also the result of failed conditionals, and vaporizes in COMPOSE/etc.

>> void
== ~void~  ; anti

>> compose [a (if false ['b]) c]
== [a c]

>> append [a b c] void
== [a b c]

>> if false ['b]
== ~void~  ; anti

>> reduce [1 + 2 maybe case [false [<a>] false [<b>]]] 10 + 20]
== [3 30]

VOID will opt out of ANY and ALL. But because of this, IF can't make a logically consistent decision about it being truthy or falsey, it gives back an error:

>> if false [10]
== ~void~  ; anti

>> any [if false [10] 10 + 20]
== 30

>> all [10 + 20 if false [1020]]
== 30

>> if (if false [10]) [20]
** Error: ~void~ antiform is neither truthy nor falsey

NOTHING is the antiform of BLANK!.

I chose the name NOTHING for the contents of an unset variable:

>> x: ~  ; will unset the variable

>> unset? $x  ; in the modern world ('x) makes a word with no binding
== ~true~  ; anti

Its meta-representation is a quasiform shown as a lone tilde (~), named TRASH. So evaluating TRASH gives you NOTHING, which has no representation in the console.

>> quasi _
== ~

>> trash? first [~]
== ~true~  ; anti

>> ~

>> nothing? ~
== ~true~  ; anti

For reasons that I have outlined, NOTHING is conditionally truthy:

>> print pick ["hello" "goodbye"] 1  ; result is nothing, no console display
hello

>> print maybe pick ["hello" "goodbye"] 3
== ~null~  ; anti

>> if (print pick ["hello" "goodbye"] 1) [<printed>]
hello
== <printed>

>> if not (print maybe pick ["hello" "goodbye"] 3) [<silent>]
== <silent>

Among the implications here, you can safely use PRINT statements in ANY and ALL (though they're not ignored, and if it is the last statement the overall result will of course be nothing... so use ELIDE PRINT if you want it to be no vote).

>> any [print "Hello", null, 1 + 2, 10 + 20]
Hello
== 3

>> all [1 + 2, 10 + 20, print "Goodbye"]
Goodbye  ; nothing result means no subsequent == in console

>> all [1 + 2, 10 + 20, elide print "Goodbye"]
Goodbye
== 30

NULL is the antiform of the WORD! "null".

In the API this is represented as the 0 pointer and does not require having its handle released, so it is like C's NULL. It is used as an "ornery nothing"...but unlike NOTHING it doesn't indicate an unset variable, so it can be fetched by normal WORD! access. The system accomplishes elegant error locality using the VOID-in-NULL-out protocol in many places, which hinges on the MAYBE function that converts NULL to void.

>> third [d e]
== ~null~  ; anti

>> append [a b c] third [d e]
** Error: Cannot put ~null~ antiforms in blocks

>> compose [all your base (third [d e]) are belong to us]
** Error: Cannot COMPOSE ~null~ antiforms into slots

>> maybe third [d e]
== ~void~  ; anti

>> append [a b c] maybe third [d e]
== [a b c]

NIHIL is an empty antiform block

This is a parameter pack with no values in it. This unstable isotope can't be stored in variables or API handles, and can only be handled in its meta form. (Explanation is beyond the scope of this thread...so read the post on modern invisibility to understand why the shade of distinction from VOID is justified.)

If you want an EVAL to vanish (in the spirit of do []) then it does so with a switch passed to it:

>> reduce [1 + 2 eval/vanish [] 10 + 20]
== [3 30]

But without the switch, it will be NOTHING and cause errors in these cases. But if the contents of the block evaluate to void, that overrides the NOTHING result and it will overall evaluate to void, and vanish in these contexts.

>> reduce [1 + 2 eval [if false [<unused>]] 10 + 20]
== [3 30]

Hopefully what I've laid out here fits what you are saying!!

  • VOID is intentional emptiness--tolerated many places as meaning "I'd like to opt out please"

    • Since it is opting out, it can't logically be considered truthy or falsey in something like IF
  • NULL is a nothingness signal, often meaning "I couldn't find what you were looking for"

    • Because it is a kind of "soft failure", it is treated as conditionally false

    • Also because it is a soft failure, most non-conditional slots reject it as an argument

    • MAYBE can be used tactically to convert NULL results to VOID

But then antiform BLANK! comes in to be NOTHING...the "bad check". A variable holding it is considered to not be set, and it trips up access via WORD!.

All three of these states can be held in variables or API handles. And then pure invisibility is built upon a weirder mechanic of NIHIL, which can only be handled by ^META-aware code. You don't need to know how it works to use it (the implementations of COMMENT and ELIDE are trivial in both the main language and UPARSE combinators). But the mechanics are there required to implement them.

1 Like

The August 2024 update of the post above represents what I consider to be the final design for this particular part of Ren-C.

is-that-your-final-answer-381x294-1

I've run it by AI and all seems good.

So if anyone can find any flaws in it...or a part that gives you pause of "is that actually right?"...now is definitely the time to say so.

Seems Obvious Now...What Took So Long?

:atom:

Isotopes had to be invented.

Before isotopes, I was just juggling around one set of non-ideal tradeoffs for other non-ideal tradeoffs.

But going through all those permutations started to give shape to what the right solution had to look like.