The Truthiness of Trash

After a couple months of being otherwise occupied, I now have some time to get back to things here...

Picking up around where I left off...I was deep into making a new bootstrap executable, as part of being able to implement the new FENCE! array type. This new executable was being aligned to "modern" conventions (though often doing a weakened version of them). Doing that triggered a lot of...thoughts. Here's another.


Does it really add value to consider the trash state (an antiform blank held by unset variables) to be neither truthy nor falsey?

Here's a quick survey of how UNSET! (the most trash-like thing in historical Rebol) is handled:

Rebol2

rebol2>> either get/any 'asdf [print "truthy"] [print "falsey"]
** Script Error: either is missing its condition argument

rebol2>> unset? all [get/any 'asdf]
== true

rebol2>> unset? any [get/any 'asdf]
== true

rebol2>> case [get/any 'asdf [print "truthy"]]
** Script Error: Block did not return a value

R3-Alpha

r3-alpha>> either get/any 'asdf [print "truthy"] [print "falsey"]
** Script error: either does not allow unset! for its condition argument

r3-alpha>> unset? all [get/any 'asdf]
== true

r3-alpha>> unset? any [get/any 'asdf]
== false  ; ...huh?

r3-alpha>> any [get/any 'asdf 1020]   
== 1020

r3-alpha>> case [get/any 'asdf [print "truthy"]]
** Script error: block did not return a value

Red

red>> either get/any 'asdf [print "truthy"] [print "falsey"]
*** Script Error: block did not return a value

red>> unset? all [get/any 'asdf]
== true

red>> unset? any [get/any 'asdf]
== true

red>> case [get/any 'asdf [print "truthy"]]
*** Script Error: block did not return a value

Oldes Rebol3

Transparent in ANY and ALL (like a Ren-C void), but presumably still an error in plain IF or EITHER or CASE...

Ren-C's "Always Error" Is More Consistent, But...

Clearly people have been pushing away from it being an error.

So does being "ornery" for conditional logic really help matters? When we consider the dual role of trash as an "uninteresting, but successful" result, might we get as much (or more?) value from considering it to be always truthy?

For example: Ren-C uses trash as the result of PRINT when the print actually produces output. Otherwise, you get NULL.

>> message: "Hello"
== "Hello"

>> print message  ; won't have a console "==" due to being a trash result
Hello

>> trash? print message
Hello
== ~true~  ; anti

>> message: null
== ~null~  ; anti

>> print maybe message
== ~null~  ; anti

>> print []
== ~null~  ; anti

Trash has the property of suppressing console output, which is desirable in most print cases--at least those that do print output. And if you said (x: print "Hello") you'd get a variable that would create an error on access, which seems also desirable.

If trash was truthy, that makes it easier to act on the trash-vs-null distinction in something like an ANY or ALL construct.

Should "Meaningless but Truthy" = "Unset Variable State"?

A related question may be if functions like PRINT should be returning the same value as what is held by an unset variable.

Now that non-antiform BLANK! itself is truthy, might it be a better choice?

>> print "Hello"
Hello
== _

But then...

  • For the visual we seek, the console would need to not print BLANK!. But I find it unsatisfying to have a non-antiform be what has "no representation".

  • If you assigned the result of this "meaningless" value to a variable, you wouldn't have that added protection that the variable would appear unset.

Truthy Trash Seems To Have More Pluses than Minuses

My "semantic safety" bias initially had made me think that when you have a function like PRINT, it's nice to catch potential mistakes when you tried to act like it was a function that could meaningfully be tested for some kind of logical result. So I pushed R3-Alpha's error from IF and EITHER further into ANY and ALL.

Then I went and made it so that when the PRINT received an opted-out input, it gave back NULL instead of trash. :face_with_diagonal_mouth: So it was something you could act on with ELSE, but not other conditional constructs.

The evolution from UNSET! to the blank-antiform that is today's trash has been a long and winding one. Working around its ornery-ness gave rise to all kinds of interesting designs like voids, and invisibles like ELIDE PRINT.

But though I'm sure that I haven't considered all the angles yet...having trash be neither-true-nor-false is looking more like a dying historical artifact than something with a clear motivation applicable to the present.

Trash being always truthy offers consistency...and it's possible to ELIDE it to get "no vote" so it won't affect an ANY or ALL (the way Oldes R3 treats unsets). Yet having the vote isn't entirely useless either. I can't think of a whole lot of downside, so I think it's worth trying.


A better axis of orneryness that may actually catch more problems in practice is: Should Trash be Illegal in Comparisons

(I point out in that discussion that simply disabling the ability to check trash for truthiness/falseyness is kind of a strange counterpart to a routine that returns--say--an INTEGER! in all cases, where you get zero information from testing an integer conditionally but without anything to stop you...making the disablement of testing trash conditionally seem like a fairly empty gesture.)

1 Like