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:
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.
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:
This was known as voidification, and it successfully avoided the behavior of inadvertently running a branch along with an ELSE clause:
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 @:
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:
The NULL-2 "isotope" undergoes "radioactive decay" to the common form, when fetched from a variable.
It also cannot escape a function unless the "as-is" form of return is used:
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.