SPACED, UNSPACED, and the FORM-ing of VOID

Historically in Rebol, the #[none] value would FORM and MOLD to appear as the WORD! none. This is still the case today in Red:

red>> form #[none]
== "none"

red>> mold #[none]
== "none"

One way of looking at this would be that as a none is a value a variable might wish to hold, that being able to show that is helpful for reflecting the state of a variable:

 red>> x: none
 
 red>> print ["The value of x is" x]
 The value of x is none

But many practical cases would be improved if it just disappeared, such as these:

red>> print ["thing to include" if 1 = 2 ["thing to omit"]]
thing to include none

red>> parentheses: 1 = 2
red>> rejoin [
        if parentheses ["("]
        "thing to maybe parenthesize"
        if parentheses [")"]
    ]
== "nonething to maybe parenthesizenone"

To say that finding a deep philosophy of PRINT has been difficult would be an understatement.

Yet ultimately the decision was to create two routines, SPACED and UNSPACED, which treat "VOID" as the absence of anything to stringify.

OPT-ing OUT of String Formation

Let's look back at the example:

>> if 1 = 2 ["thing to omit"]
== \~,~\  ; antiform (void!)

>> print ["thing to include" if 1 = 2 ["thing to omit"]]
thing to include

>> parentheses: 1 = 2
>> unspaced [
        if parentheses ["("]
        "thing to maybe parenthesize"
        if parentheses [")"]
    ]
== "thing to maybe parenthesize"

PRINT invokes SPACED by default if given a block, and if one wishes to get an unspaced print you simply say print unspaced [...].

The result has been a profound improvement. Says @ShixinZeng:

I love it. It's much easier to write than either true ["something"][""]

You too will love it. It's much better than REJOIN. You're going to use it again and again.

Feel free to add your testimonials here.

1 Like

I'll also mention that not only is print unspaced [...] more literate and comprehensible than print rejoin [...] from a terminology standpoint, the fact that UNSPACED always returns a string is important.

Note how the property of REJOIN adopting the type of its first element can cause unpredictable variations, that would throw off new users:

>> x: [some stuff]

>> print rejoin ["the data is" space x]
the data is some stuff

>> print rejoin [x space "is the data"]
*** Script Error: some has no value
*** Where: print

I found out that there's some parallel code in some parts of Red with something called WHEN, which returns an empty BLOCK! on failure:

red>> when 1 = 2 ["thing to omit"]
== []

Since they splice in many places by default, this can sort of work... sometimes. Although since blocks are a "thing" you will get in situations that care more about the thingness than they do about the emptiness.

One thing you can do with that model is put the block into a variable, and use it by reference directly, without an operator like OPT. Ren-C can do a better version of this with empty SPLICE! antiforms:

>> foo: spread []
== \~()~\ ; antiform (splice!)

>> unspaced ["a" (foo) "b"]
== "ab"

But splices are truthy, hence an empty splice is truthy too:

>> if foo [print "This runs"]
This runs

So by and large I prefer to have variables hold null so they're falsey, and use OPT to turn NULL into VOID at sites I want to vaporize:

>> bar: null
== \~null~\  ; antiform (keyword)

>> unspaced ["a" (opt bar) "b"]
== [ab]

>> if bar [print "This branch won't run"]
== \~null~\  ; antiform (keyword)

Most of the time, I think it's also better to have a cue when looking at the variable reference sites to know "hey, this thing might vaporize".

But it's nice to have all the bases covered...and have a generic empty state that can be referenced without an OPT, if you need it!

1 Like