What should evaluating an empty block (`EVAL []`) do?

(Note: I've pared the above conversation down to the minimum, updating it with 2024 terminology and behaviors.)

There's a few interesting developments that inform the thinking on this...

The State Held by Unset Variables (e.g. NOTHING) Is Now Truthy

>> if get/any $asdf [print "Ren-C considers NOTHING as truthy"]
Ren-C considers NOTHING as truthy

This decision aligns with Red's choice for UNSET! (though Rebol2 and R3-Alpha consider trying to conditionally test UNSET! as an error).

Passing NOTHING to Comparison Operations Now Fails

>> if (10 = get/any $asdf) [print "Not legal in Ren-C"]
** Script Error: = expects [something?] for its value2 argument

This decision aligns with Rebol2's treatment of UNSET! in comparisons (though Red and R3-Alpha allow it).

FUNC returns NOTHING unless you use an explicit RETURN

A side effect of this is that it's a lot harder to accidentally return VOID from a function:

 whatever: func [x [block! group!]] [
     if block? x [append x spread [a b c]]
     if group? x [append x spread [d e f]]
 ]

Under the historical behavior, if you pass in a GROUP! the final function result would be the branch result of the APPEND which will be the group with [d e f] added. But if you passed in a BLOCK! then the IF GROUP? test would be false and the overall return result would be a VOID.

VOID's Properties Are the Same as They Have Been

  • Legal in comparisons

  • Illegal in isolated conditional testing

  • Opts-out when testing conditionally in aggregate (e.g. ANY and ALL).

What Are The Implications For (EVAL []) ?

  • FUNC Change Means The World Overall Is More Full Of Nothing - In this case it's a good thing. The fewer accidental VOIDs that are being produced in the ecology overall, the better I feel about constructs like EVAL being willing to produce them, or for things like REDUCE (or anything else) being willing to discard them.

  • NOTHING Is In Some Ways Less Ornery - if the goal of eval compose [...] when all the material vaporizes was to give back an "ornery" result, it's worth noting that NOTHING is less ornery than it used to be in conditionals (and more ornery than it used to be in comparisons).

    • Were you to write if eval compose block [print "Truthy"] and all the expressions in BLOCK vaporized when this wasn't your intent, VOID "offer more safety" now than NOTHING

    • But if you wrote if 10 = eval compose block [print "Truthy"] and all the expressions in BLOCK vaporized when this wasn't your intent, NOTHING would "offer more safety" than VOID

    I know this seems silly but that's my point. Choosing to return NOTHING for eval [] is useless (on purpose)... so its only theoretical value is to offer some safety on shooting yourself in the foot with VOID when something like a REDUCE'd or COMPOSE'd block decays to no elements. But that safety seems a bit of a mirage, and also it prevents you from exploiting useful VOID results when you want that from the decay.

Honestly, It's Barely Come Up... Yet

Which is an indication it doesn't happen on accident. So the main way it's going to come up is if people know that's the behavior, and start designing code that purposefully uses the pattern.

And if they do that, then that is a good thing, because they're getting use out of it?