Rebmake Shows Why NULLs Shouldn't Vaporize in REDUCE

Before there were distinct NULL and VOID states, there was only NULL... and it was the product of failed conditionals. There was a debate about whether NULL should be an error if encountered by REDUCE, or if it should vaporize. This is a little piece of circa 2021 history I extracted about the first case I saw that represented a problem, that I edited out of a thread I was paring down.


Both changes are now in master:

The new REDUCE behavior has been advocated now by @rgchris, @BlackATTR, @giuliolunati, @gchiu, and was my original choice also:

>> append [<a> <b>] reduce [<c> if false [<d>]]
== [<a> <b> <c>]

Up until now it has errored to leave the option open, without yet breaking the "N expressions in, N values out" dogma espoused by DocKimbel.


Hmmm...well when I tried bootstrapping the updated executable, here is an example of where NULL vaporizing in REDUCE bit me:

It was some of Shixin's code from rebmake.

    if not let suffix: find reduce [
        #application target-platform/exe-suffix
        #dynamic-library target-platform/dll-suffix
        #static-library target-platform/archive-suffix
        #object-library target-platform/archive-suffix
        #object-file target-platform/obj-suffix
    ] project/class [return]

    suffix: second suffix

I had changed the suffixes in the base class of some objects from BLANK! to NULL. This was in order to be more likely to catch usage problems of those suffixes, when BLANK! is more quiet about many operations (e.g. they will silently append, like classical #[none] would).

NULL provides a gentle sort of alarm...in the sense that it is falsey and can't be e.g. silently appended without an operation converting it to a value. This is good for callsite comprehension.

But with NULL vanishing here, code in this style has problems. I'm not sure there's anything particularly wrong about code in this style. So we still might want to think about this.

Looking at this through 2024 eyes, clearly there is something wrong with code in this style ... it can't/won't work.

What's good about NULL erroring here is it at least lets you know that what you're doing is broken.

Use a switch statement!

let suffix: switch project.class [
    #application [target-platform.exe-suffix]
    #dynamic-library [target-platform.dll-suffix]
    #static-library [target-platform.archive-suffix]
    #object-library [target-platform.archive-suffix]
    #object-file [target-platform.obj-suffix]
 ] else [
    return ~
 ]

But if you really want to make a block for some reason, you can round-trip your antiforms with REIFY and DEGRADE.

>> suffix: null
== ~null~  ; anti

>> reduce [#application suffix]
** Script Error: non-NULL value required (see MAYBE, TRY, REIFY)

>> reify suffix  ; makes a quasiform out of antiforms, passes all else as-is
== ~null~

>> reduce [#application reify suffix]
== [#application ~null~]

>> second reduce [#application reify suffix]
== ~null~

>> degrade second reduce [#application reify suffix]
== ~null~  ; anti

So long as you know the data you're working with doesn't have quasiforms that you wanted to pick out as quasiforms, you don't have to use META and UNMETA... meaning only your antiforms will get transformed, vs. adding quotes to everything else.

Sorry to those for whom this seems like tough love. But trust me from experience (which has come from trying to maintain things like Rebmake!) that if you only have reified nothings, then you wind up making a mess when you get confused about when something that isn't supposed to be a thing gets treated as a thing.

Antiforms are your friends. They're here to help.

:handshake:

Just to reiterate here, this does work... but it works because the failed conditional now returns a ~void~ antiform instead of NULL... and REDUCE is willing to throw out the VOID.

But people don't go around putting voids in variables to indicate "nothing here". Not only because vaporization isn't the intent, but also because you can't conditionally test voids with IF, CASE, etc.