The Limited and Ambiguous Historical Idea
People are used to being able to do things like:
x: 10
switch type? x [
integer! [print "It's an integer"]
block! [print "It's a block"]
]
assert [parse [1 [second] 'foo] [integer! block! lit-word!]]
assert [find any-word! (type? first [x:])]
But the historical DATATYPE! and TYPESET! were strange.
-
DATATYPE! rendered as a WORD! but was really wrapping an integer of 0-63
-
TYPESET! was a 64-bit bitset, one bit for each type (this is where the 64 types limit came from)
-
it lost its meaning in rendering (it kept no record of what the set actually was...just dumped words for each bit)
-
not preserving the name from a fixed list of typesets was based on the concept you could make your own or UNION/INTERSECT them
-
So it looked like this:
red>> type? 1
== integer!
red>> type? type? 1
== datatype!
red>> print mold any-word!
make typeset! [word! set-word! lit-word! get-word!]
red>> print mold any-type!
make typeset! [datatype! unset! none! logic! block! paren! string! file! url!
char! integer! float! word! set word! lit-word! get-word! refinement! issue!
native! action! op! function! path! lit-path! set-path! get-path! routine!
bitset! object! typeset! error! vector! hash! pair! percent! tuple! map!
binary! time! tag! email! handle! date! port! money! ref! point2D! point3D!
image! event!]
The TYPE-XXX! Approach
So Ren-C attacked the ambiguity and extensibility with a new word type, TYPE-WORD!. Then typesets used TYPE-GROUP! and TYPE-BLOCK!, referencing functions to act as type testing predicates, and using groups for intersections and blocks for unions:
>> type of 1
== &integer
>> type of type of 1
== &type-word
>> print mold any-word!
&(any-word?)
>> print mold any-value!
&(any-value?)
This gives some realistic axis of extensibility, and gives distinguishable entities that can trigger behaviors in PARSE when something looks up to type-xxx!. (this shows why using WORD! or URL! or ISSUE! wouldn't work, because the type intent has to be carried by what e.g. INTEGER! looks up to.)
Calling functions to implement type checks vs. checks on a bitset, especially when an array of functions must be called when checking every parameter in every function call, is a difficult performance point.
Intrinsics and other magic are employed to rein it in. It's not particularly simple...but finding ways to speed up function calls where you can has systemic benefit.
New Consequence: FIND Must Find TYPE-WORD! Normally
Being a legitimate datatype that can be stored in a block, some interpretations of datatype by functions like FIND were problematic:
red>> block: reduce ["hello" integer! 1]
== ["hello" integer! 1]
red>> find block 'integer!
== none ; rendering was a lie
red>> find block integer!
== [1]
You couldn't find a literal datatype in a block. Ren-C is approaching this by saying FIND has to find the TYPE-WORD! (as it does for all non-antiforms), but that you can use antiform actions as predicates.
>> block: reduce ["hello" integer! 1]
== ["hello" &integer 1]
>> find block integer!
== [&integer 1]
>> find block :integer?
== [1]
There was some thought that maybe you could create antiform TYPE-XXX! and call them "matchers", passing them to FIND.
-
But this is an isotope for each TYPE-XXX!, so it's not even like there would be one "matcher"
-
It also would be the only instance of antiforms of types with sigils, which doubles the sigil to make
~&integer~
, which I find kind of displeasing
I feel that antiform actions cover it for FIND, and if you have higher level needs you should use something like PARSE which has richer options and isn't beholden to quite the "mechanical" answer that a series primitive like FIND has to abide by with its limited parameterization.
New Annoyance: TYPE OF Quotes And Antiforms
When there were only two datatypes with quotedness, the quote was part of their datatype:
red>> type? first ['a]
== lit-word!
red>> type? first ['a/b]
== lit-path!
red>> lit-word! = type? first ['a]
== true
red>> parse ['a 'a/b] [lit-word! lit-path!]
== true
Ren-C's approach affords the ability make type constraints to carry forward the PARSE behavior. But the TYPE OF all quoteds is the same... "ED.
>> lit-word?!
== &(lit-word?)
>> lit-word?! = type of first ['a]
== ~false~ ; anti
>> type of first ['a]
== "ed
So perhaps you see the motivation to decorate as ?! instead of just ! for the type constraints. People need to know that these aren't fundamental types. You have to use e.g. MATCH with them:
>> match lit-word?! first ['a]
== 'a
>> match lit-word?! 10
== ~null~ ; anti
>> match [lit-word?] first ['a] ; alternative as 1st slot known "typelike"
== 'a
This is something of a pain point, and I'm not entirely settled on whether it would be good to delve into some kind of ambiguity where we are actually allowed to get back constraint functions as the answer to TYPE OF, and make that the fundamental:
>> type of 1
== &integer?
>> type of spread [a b]
== &splice?
>> type of ~true~
== &logic?
>> type of first ['a]
== "ed?
So I don't think this is a good idea for the quoted types, but for the antiforms it might be a narrow enough thing that it provides "what the people want".
>> switch type of true [
splice! [...]
logic! [...]
integer! [...]
]
Barring that, what we have to do today is flip SWITCH over into a MATCH mode (currently called SWITCH/TYPE but should probably be SWITCH/MATCH... or maybe it should take the MATCH name):
>> switch/type true [
splice?! [...]
logic?! [...]
integer! [...]
]
Note that the ?!
distinction is a new idea which hasn't made it to all type constraints yet, e.g. ANY-VALUE! is still as it was. But because parameters use what is effectively a TYPE-BLOCK! you can say any-value?
or splice?
in them instead of going through the extra step.
Should TYPE Be A Bigger Concept?
One thing that has nagged me is if when we ask for the fundamental "cell type" of something, if we should avoid using the word "TYPE" for that at all...
Maybe there's some bigger idea in an object/class system where TYPE is meaningful to say something more than "this is an object" but rather "this is a book", where you can ask also "is a book readable". Etc.
Or maybe TYPE can be parameterized:
>> type of matrix
== &[matrix 10x10]
So this would mean there's a smaller question about the fundamental type, maybe call it "KIND":
>> kind of [a b c]
== &block
>> kind of matrix
== &object
It would be nice to just be able to say "64 types is enough for anyone" and say "there, it's done". I'd be happy to do that if I felt that it was enough. It wasn't, even when thinking along fairly limited lines that don't go in these fancier directions.
I don't think any near-term system will actualize on bigger visions of what TYPE might be, but it would help to know if that should be ruled out or not, just in order to pick the term KIND or TYPE! But even that question is murky.
Some Related Reading: %types.r
The dialected table used to construct the type testing macros and other things is kind of neat, though some comments are out of date and parts of it need updating (it's getting upgraded in an upcoming commit which finally breaks the 64-type barrier and introduces the $ types):
See %types.r