metaeducation / ren-c

Library for embedding a Rebol interpreter into C codebases
GNU Lesser General Public License v3.0
128 stars 27 forks source link

NULL Isotopes (The End of Voidfication!) #1099

Closed hostilefork closed 4 years ago

hostilefork commented 4 years ago

Historically there has been a struggle over the potential conflation of NULL as a special signal ("no branch ran", "loop encountered a BREAK") and "branch/body evaluated to null".

The "simple" technique used to help distinguish these cases was to reserve NULL for the signal, and mutate any branch or body result to a VOID! value so it would not collide:

>> if false [<whatever>]
; null

>> if true [null]
== ~branched~

This was known as voidification, and it successfully avoided the behavior of inadvertently running a branch along with an ELSE clause:

>> if true [print "branch", null] else [print "this shouldn't run"]
branch
== ~branched~

However, it caused collateral damage to the return result. The answer given for users who did not want voidification applied was to mark branches "as-is" using @:

>> if true @[null]
; null

Despite the availability of a workaround, every branch usage in the system wound up paying a tax for the feature. A new approach was sought, and ultimately found...which builds on the old ideas.

The new concept of "NULL ISOTOPES" means there are two versions of NULL, where most users need not worry which version they are using. Basically wherever a voidification would have occurred before, a NULL is canonized as the "heavy" version (NULL-2) that will trigger a THEN but not an ELSE:

>> if false [<whatever>]
; null

>> if true [null]
; null-2

>> null? if true [null]
== #[true]  ; NULL-2 answers true to NULL?

>> if true [null] else [print "ELSE not triggered"]
; null-2

The NULL-2 "isotope" undergoes "radioactive decay" to the common form, when fetched from a variable.

>> x: if true [null]
; null-2

>> x
; null

It also cannot escape a function unless the "as-is" form of return is used:

>> foo: func [] [return null-2]
>> foo
; null

>> bar: func [] [return @(null-2)]
>> bar
; null-2

This means casual users who are not writing control constructs that may need to distinguish NULL-from-code and NULL-as-signal should not be bothered by it. All their NULLs will be signal NULLs unless they wish to get otherwise involved.