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>] spread reduce [<c> if null [<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 null]

    suffix: second suffix

I had changed the suffixes in the base class of some objects from being 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 null
 ]

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~\  ; antiform

>> reduce [#application suffix]
** Script Error: non-NULL value required (see OPT, LIFT, 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~\  ; antiform

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 LIFT and UNLIFT... 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: