Where to use BLANK vs TRASH

I realized while thinking about what the right default for ARRAY would be, that there are two potentially sensible choices for a default... both of them single-character reified placeholders:

[1]  >> array 3
     == [_ _ _]  ; BLANK!s

[2]  >> array 3
     == [~ ~ ~]  ; Quasi-BLANK!s ("TRASH!")

This Offers Us Some Nuance Even If A State Must Be Reified!

When it comes to the direct behavior of APPEND to a block, these states have to work the same. In the era of isotopes, all reified values are appended as-is... it cannot (and should not) be any more complex:

>> append [a b] second [c _]
== [a b _]

>> append [a b] second [c ~]
== [a b ~]

>> append [a b] second [c [d e]]
== [a b [d e]]

But when we throw in an extra operation, we can imagine a difference. For instance, we could make BLANK! semantically equivalent to an empty array for the purposes of things like SPREAD or EMPTY?

>> spread second [c []]
== ~()~  ; anti

>> spread second [c _]
== ~()~  ; anti

>> append [a b] spread second [c _]
== [a b]

>> empty? second [c _]
== ~true~  ; anti

...and then we'd say that if you tried to do such things with a "meta-nothing" trash state, it would be an error:

>> spread second [c ~]
** Error: SPREAD does not accept ~ as an argument

I think this suggests that ~ makes a better choice for the default value of ARRAY elements! We can't default to an antiform like the one representing unset variables, but it's the closest thing.

Ultimately it came to seem that having only the antiform ~null~ be a "branch inhibitor" was more valuable than having BLANK! be a "branch inhibitor". Simply being able to assume that anything you can find in an array will run a branch offered more leverage. So blanks are "branch triggers" now, BUT they're empty.

What About Opting Out Of As-Is Appends, etc?

I mentioned that all items that can be found in a block have to act mechanically identically when it comes to TAKE-ing and APPEND-ing them. But what would XXX be if you wanted the following?

>> append [a b] xxx second [c [d e]]
== [a b [d e]]

>> append [a b] xxx second [c _]
== [a b _]

>> append [a b] xxx second [c ~]
** Error: Cannot append NOTHING (~ antiform) to a block

I'm trying having this operator be called DEGRADE. It would turn all quasiforms into their corresponding antiform:

>> degrade first [~null~]
== ~null~  ; anti

>> degrade first ['foo]
== 'foo

>> degrade first [123]
== 123

The reverse of this operator would be REIFY.

What About FOR-EACH Variations?

I think an additionally neat spin on how these can be treated differently can be how FOR-EACH responds.

>> for-each x (second [c []]) [
       print "Loop never runs"
   ]
== ~void~  ; anti

>> for-each x (second [c _]) [
       print "Loop never runs"
   ]
== ~void~  ; anti

>> for-each x (degrade second [c ~void~]) [
       print "Loop never runs"
   ]
== ~null~  ; anti (like a void in, null out... or if a BREAK was hit)

>> for-each x (second [c ~]) [
       print "Loop never runs"
   ]
** Error: FOR-EACH does not accept TRASH as its data argument

This is a bit more speculative, but I like the general idea that a quasi void could let you have a kind of nothing that gave you the "opt out" ability in places where it could... and quasi blank could give you an error, while blank acts like an empty series. This seems to offer some nice invariants that reduce overall code you have to write handling edge cases.

2 Likes