At this exact moment (in Sep 2022)...TYPE OF any antiform is an error, while both TYPE OF NULL and TYPE OF VOID give back NULL.
OF is a generic operation (the enfix form of REFLECT that quotes the word on its left. It may be desirable to honor the VOID-in-NULL out convention for all the other reflectors that aren't TYPE... and it's desirable to error on NULL more generically.
>> label of null
** Error: You didn't give anything
>> label of maybe null ; MAYBE NULL is VOID
; null
So if type of follows the same pattern as other xxx of, we'd surmise that you don't use TYPE OF to discern NULL and VOID. It errors on NULL input and gives you back NULL if you MAYBE it.
But what happens when you ask:
>> spread [d e]
== ~(d e)~ ; anti
>> type of spread [d e]
???
The Original Plan Was No Arguments Received Antiforms
In the original conception, function frames weren't capable of holding antiforms in the arguments. You physically could not receive a parameter that was an antiform.
I was also looking at the idea that some antiforms--such as raised ERROR!--would be completely impossible to get into a variable, ever.
The only workaround was if a function used the ^META parameter convention, in which case an antiform would come in as a QUASI! form of the value...while normal values would come in as one level quoted higher than they were:
>> detector: func [^x] [print ["Meta of X:" mold x]]
>> detector [d e]
Meta of X: '[d e]
>> detector spread [d e]
Meta of X: ~(d e)~
Ultimately I backed down on this, instead allowing you to use type predicates to narrow which antiforms you'd be willing to accept:
>> splicetaker: func [x [any-value! splice?]] [
append [a b c] :x
]
>> splicetaker [d e]
== [a b c [d e]]
>> splicetaker spread [d e]
== [a b c d e]
A primary driver behind this change was that operations which wanted to do things like ADAPT a function frame were having to become sensitive to whether a parameter was ^META or not. It seemed that standardizing the frame in a way that permitted antiforms as currency made more sense than having arguments be sometimes-meta'd, sometimes not.
(Note: A later driver of this was that LOGIC became implemented with antiforms, and needing to make a parameter meta to take logic was another bridge-too-far.)
What if OF (REFLECT) Didn't Take Antiforms?
So we could say that if you think you have an antiform in your hand, you're responsible for ^META-ing it yourself:
>> metatyper: func [x [any-value! splice?]] [
print ["Metatype of X is" type of ^x]
]
>> metatyper [d e]
== &['block] ; the TYPE OF received a QUOTED!, so e.g. answer incorporates quoted
>> metatyper spread [d e]
== &[~block~] ; got QUASI!, so TYPE OF answer incorporates quasi
On the plus side of such an approach, we don't have to invent any type representations for antiforms.