Old Idea Revisited: DATATYPE as... Antiform?

I've been suffering through an attempt to bring sanity to bear upon the MAKE and TO matrix.

While doing so, I noticed that there are a lot of MAKE instructions that do "joining".

In R3-Alpha and Red:

>> make binary! [1 2 #{DECAFBAD} 3 4]   
== #{0102DECAFBAD0304}

Historical MAKE is so arbitrary in how it interprets its arguments that it made me feel that it would be better if JOIN allowed a datatype as its first argument...

JOIN typically makes a new series out of what you give it:

>> bin: #{AABB}

>> join bin #{CCDD}
== #{AABBCCDD}  ; made new series, didn't impact BIN

>> bin
== #{AABB}

But if you could pass a datatype as the first argument, you could relieve MAKE of the behavior...and make it clearer in the source what's actually happening:

>> join binary! [1 2 #{DECAFBAD} 3 4]
== #{0102DECAFBAD0304}

Since that seemed nice, I started to map it out, and ran into a bit of a problem:

If "datatypes" are an ANY-WORD! or ANY-LIST! or anything like that, how do you know you're not trying to JOIN with it?

A WORD! should be able to join:

>> join 'var 10
== var10

A BLOCK! should be able to join... though possibly only with blocks that it reduces (devil's in the details as to why, beyond the scope of what I'm talking about right now)...

>> join [a b] [1000 + 20]
== [a b 1020]

Okay, but when you say join binary! what is BINARY!? Well right now, it's:

>> binary!
== &[binary]

Well that looks like it's in-band for the kinds of things JOIN operates on:

>> join &[binary] [1000 + 20]
== &[binary 1020]

An Antiform Would Push DATATYPE? Out-Of-Band...

It's possible that we could come up with a new non-antiform for types. But the lexical space is extremely saturated.

If we were just reaching for something on the basis of looks, antiform FENCE! is nice:

>> binary!
== ~{binary!}~  ; anti

UPDATE: @BlackATTR and I believe the exclamation point is probably best as part of the contents. If you're turning a datatype into a word, the only reason you're doing it is to present to a programmer. This lines up with history:

red>> to word! integer!
== integer!

rebol2>> to word! integer!
== integer!

This Has Come Up Before... (in 2022)

Historical Rebol let you use datatypes in FIND:

rebol2>> find [a b c 10 20] integer! 
== [10 20]

But then this conflates with when you actually literally have the INTEGER! datatype in the block.

>> find compose [a (integer!) b c 10 20] integer!
== [integer! b c 10 20]

So in the early days of antiforms, I proposed antiform datatypes as a solution... but at that time I was thinking that there'd be non-antiform DATATYPE!, and it's just that when it was an antiform it would be a "MATCHER".

The proposal I'm thinking of now is different, in saying that DATATYPE? only exists as a (stable) antiform state.

There are tradeoffs, in that datatypes can't be put in blocks directly. But the mechanics of working with quasiforms and antiforms via things like REIFY and DEGRADE are much smoother now. And really, putting datatypes in blocks doesn't happen that often (I've had to put splices in blocks...and they're antiforms... but really never datatypes).

Being completely out of band with things you can find in a block has advantages. JOIN isn't unique in reaping benefits from being able to say that datatypes aren't "things".

And it unifies datatypes with typecheckers being antiforms, because typecheckers are frame antiforms, e.g. actions:

>> find [1 3 5 8 10] even?/
; first in pack of length 2
== [8 10]

So I don't think it's a crazy idea.

I'm hesitant to surrender FENCE! antiforms to the purpose so soon after their invention. But there isn't anything undecorated left.

Not that "no decorated antiforms" is some unbreakable rule, it was a guideline.

Using & would put it in the family of TYPE-XXX! and be cheaper than FENCE! if you made new instances:

>> binary!
== ~&binary!~  ; anti

A random bad idea would be to make them keywords, and just have DATATYPE? be smart enough to filter out ~null~ or ~void~ or ~okay~ or whatever else (the symbol could carry a flag)

>> binary!
== ~binary!~  ; anti

>> null
== ~null~  ; anti

>> datatype? binary!
== ~okay~  ; anti

>> datatype? null
== ~null~  ; anti

I can pretty clearly say that's bad.

Or, Pick Some Non-Antiform Notation

Status quo might look like:

>> binary!
== #[binary!]

Despite it looking sort of like a block, it wouldn't be. But we're squandering some of our notational space no matter what. The ~&type!~ is the discount route.

Either Way, DATATYPE? Can't be ANY-WORD! or ANY-LIST!

It needs to be out of band one way or another. I'll have to think about it.

1 Like

I like this idea. You know I’ve been somewhat unhappy about the way Ren-C currently represents datatypes; making them antiforms feels right to me, and could act as a good step towards a full solution.

1 Like

Join the club :slight_smile: But I've sort of moved in the direction of accepting that TYPE OF really just answers the fundamental type question, and other questions need to be more specific probes.

There's lots of choices (CLASS OF, or other fingerprinting routines).

"type of antiform" now possible... Should We Use It?

Been ruminating on it, and I think it's probably the right answer.

I've made the arguments above. But beyond the argumentation there's just sort of an intuition... that the answer to the TYPE OF question isn't something you think of as "in-band" in terms of what you generally think of as a "fundamental" value... e.g. one you can put in a block.

You've moved out of the domain of "things" and into a sort of domain of "meta things" when you ask a type question.

And of course, the original motivator still applies... which was the ambiguity of:

rebol2>> find [a 1 b c] integer!
== [1 b c]

rebol2>> find compose [a (integer!) b c] integer!
== [integer! b c]  ; not a word!, should render as [#[integer!] b c]

If INTEGER! is an antiform, then you don't have to worry about that ambiguity, since such a COMPOSE is impossible.

So it's a little funny that my gut reaction would be to be afraid to surrender FENCE!. Datatypes are really important, so "sacrificing" antiform fence for them shouldn't be the issue. If antiform FENCE! somehow solved the datatype problem it's not a sacrifice--it's a solution!

The question should be about fitness for purpose...does it work or is it advantageous to use a list.

Originally the choice to make datatypes like &[integer] instead of words as &integer was that I wanted to use &some-function? as an inert type that could be passed to things like MATCH or use in PARSE.

parse [a @b $c] [some &any-word?]

match &any-word? 'a

; vs...

parse [a @b $c] [some &[any-word?]]

match &[any-word?] 'a

Since datatypes existed under names, they wouldn't be used literally very often--but referred to from words as integer! instead--there was no real motivation to give datatypes the more succinct form. The brackets wouldn't hurt.

I now believe that type constraints are more attractively (and consistently) done with trailing slash:

parse [a @b $c] [some any-word?/]

match any-word?/ 'a  ; slash as barrier hints 'a is not an argument

It does mean that type constraints aren't a different entity from plain actions. But so far, it seems anywhere that was expecting a datatype or type constriant doesn't have any other overloaded meaning for actions...the action would always be interpreted as a test. And with intrinsics and the typeset table optimization it's no longer necessary for performance reasons to distinguish them.

Beyond that, there was some speculation that the answer to TYPE OF might expand out... an example might be forms that included the number of quotes.

 >> type of first ['''x]
 == &[quoted! quoted! quoted! word!]

My updated thinking is that this is a different question from what you get back as a default from TYPE OF.

How Would This Play Out With Antiform FENCE?

So if we believe that the new antiform DATATYPE! might store such answers, then using a list for it would be necessary:

 >> type:quotes of first ['''x]
 == ~{quoted! quoted! quoted! word!}~  ; anti

(Strangely enough, even with two tildes and an "anti", that does look better than the & sigil.)

The question arises: can this live up to the expected behaviors of DATATYPE?

>> qtype!!: type:quotes of first ['''x]
== ~{quoted! quoted! quoted! word!}~  ; anti

>> parse [a '''b] [word! qtype!!]
== '''b

The system could certainly do this for some limited set of representations. Maybe not that limited, if it dispatched as a generic to the datatype to ask if it matched.

Equality tests are out the window, though.

>> type-a: type of first ['''x]
== ~{quoted!}~  ; anti

>> type-b: type:quotes of first ['''x]
== ~{quoted! quoted! quoted! word!}~  ; anti

>> type-a = type-b
== ~null~  ; anti

There might be some limited set of ways to ask if they were "like":

>> type-a like type-b
== ~okay~  ; anti

And maybe that could be driven by logic of checking just the first element in the datatype fence's array. Or maybe that's something else methodized via generic. :man_shrugging:

Anyway, this would all motivate the idea that the list-like nature of fence for datatype is justified.

I'm Going Ahead With It

While it's hazy to think of exactly how this will work out, I'm going to likely be killing off the datatype-oriented usage of & - types.

The & types may remain and be used for some other purpose. Put a pin in that. :pushpin: Rather than delete them, for now I'm just going to call them WILD-WORD!, WILD-BLOCK! etc. and make them error in the evaluator. Worst case scenario they're just a type you know has no other meaning, so your dialect can ascribe whatever you want while delegating the other behaviors to the evaluator. But maybe there is a missing fundamental behavior I haven't identified yet and these can solve that problem.

Afterthought

It occurs to me that I've spoken about FENCE! as being tied in intimately with MAKE

MAKE Should Be Using *Dialected Constructors*

This sort of connects antiform/quasiform FENCE! with datatypes, in a way, if you squint at it.

2 Likes

So I was noticing you couldn't do this anymore...

func compose:deep [x [word! (integer!)]] [ ... ]

Because integer! is an antiform of ~{integer!}~, and you can't put antiforms in blocks...

But let's say you ^META it...

>> compose:deep [x [word! (meta integer!)]]
== [x [word! ~{integer!}~]]

We have sort of free reign to decide what quasiforms do in type spec blocks. For instance, quasi-blocks match packs with those types, e.g. ~[integer! block!]~ would match a pack antiform like ~['1 '[a b c]]~. So it's not like the quasiform states are uniformly treated literally.

So why not have quasi-fences mean typecheck that as a datatype?

Seems like a winner to me (and reminiscent of my recent realization of "Quasiforms can mean whatever we want to design them to do in PARSE")

TYPE OF including Quoted/Quasi Status?

In the dawn of generalized quoting, I thought about putting quote marks on the datatype to reflect how much quoting there was:

>> type of first ['''a]
== '''[#datatype word!]

But I realized having quotes on the outside of the type had bad properties... it was no longer a datatype, it was a QUOTED!. And if you put it somewhere and it evaluated those quotes were easy to lose track of.

So as using a list type came up, I thought putting the quotes inside the list made more sense.

>> type of first ['''a]
== &['''word!]

But with the dawn of antiforms and quasiforms, there was a problem. If you wanted to signal an antiform you'd perhaps do that with a quasiform.

>> spread [a b c]
== ~(a b c)~  ; anti

>> type of spread [a b c]
== &[~group!~]

But then your answer for quasiforms would have to be quoted...and your answer for everything else would be one level quoted higher than you wanted it:

>> type of first [~a~]
== &['~word!~]  ; :-(

>> type of first ['''a]
== &[''''word!]  ; four quotes?!  :-(

That was hideous. But as of now, antiforms are getting their own types:

>> type of spread [a b c]
== ~{splice!}~

Which means maybe the old system could be used more sensibly:

>> type of first [~a~]
== ~{~word!~}~

>> type of first ['''a]
== ~{'''word!}~

That doesn't look hideous. In any case, I proposed the idea that you could produce these datatypes from other ones, e.g.

>> integer!
== ~{integer!}~

>> quoted integer!
==  ~{'integer!}~

>> quoted-datatype? ~{'integer!}~
== ~okay~  ; anti

What motivated me to think about TYPE OF giving these distinct answers e.g. for quasiforms and quoteds is that I want PARSE to be able to dispatch on quasiforms now, and it doesn't have a good way to do that unless it distinguishes them.

Again--this is an old idea reviewed in light of new developments. It doesn't look completely nuts. I'm in the throes of the antiform datatype conversion, so I'll probably have more to say shortly.


UPDATE: The antiform datatype code boots after less than 2 days work! But... booting is the beginning of a lot of details and additional design work.

Finally! You know how I loathed those &-types. I never did manage to work out a replacement for them, but antiforms are a wonderfully clever solution — as you say, it makes perfect sense semantically, and eliminates problems with putting them in blocks. I think this approach could solve a lot of issues.

2 Likes

I'm wondering now, what the application of & should be.

The @xxx and $xxx types became integral to solving binding. I don't think there's another binding-related type needed (though I could be wrong).

But one of the big weak spots in the system is lifetime management. I have a vague fuzzy idea that maybe it would be related to something in the realm of & as used for borrow checking in Rust.

>> &[bl o ck]
== [bl o ck]  ; but with some manipulation to its lifetime rules

>> some-func &var
; pass function fetched value that has had that manipulation applied

The language aesthetics hinge on this being an uncommon case (e.g. you can't need to do this for something fundamental like append &block &item or the language starts looking like a mess.) But I'm going to keep an eye out for how it might be used.

In any case, you may not have heard the last of &xxx types. :slight_smile:

It occurred to me that decorators could be put on the types:

>> type of first [@a]
== ~{@word!}~  ; anti

>> the-word!
== ~{@word!}~  ; anti

This encodes the sigil directly into the datatype. It has the drawback of screwing up TO WORD! datatype's implementation, however.

You should be able to ask for the sigil that a datatype uses, but I'm not sure I like that being done with SIGIL OF:

>> sigil of the-word!
== @

Maybe SIGIL-OF:TYPE.

>> sigil-of:type the-word!
== @

Miles to go with this, but it does look more hopeful than it has.

Not that any of this is completely understood yet, but another advantage is potentially in terms of extensibility, since you can put any value in a list but not a sigil on any value.

So perhaps extension types are TUPLE!s or URL!s or something:

>> import http://github.com/Oldes/vector/vector.r  ; (Oldes loves vector!)

>> type of make vector! [...]
== ~{oldes.vector!}~  ; anti

; or something like

>> type of make vector! [...]
== ~{vector! http://oldes-r3.com/extensions/}~  ; anti

While I abandoned extension types for a while because of the mess they were making, it might be the right time to bring at least one of them back. But I don't want them to be in the core repository, so there'd have to modifications to the build process to bring things in from other repos.

(I've not yet taken that step, only put issue databases up for things like the ODBC extension where issues can be filed there, it's still maintained in the core repo for now.)

One curious observation about datatypes being antiforms is that there is no meaning currently to QUASI or QUOTE of an antiform.

>> spread [a b c]
== ~(a b c)~  ; anti

>> quote spread [a b c]
** Error: You can't quote antiforms, fool.

>> quasi spread [a b c]
** Error: You can't quasi them either!  META it if you want to make ~(a b c)~

But what if an exception were made for datatypes... not to make them non-antiforms, but to integrate the quoted state into the datatype?

>> blank!
== ~{blank!}~  ; anti

>> quasi blank!
== ~{~blank!~}~  ; anti

>> quote quote blank!
== ~{''blank!}~  ; anti

Then in typespecs, you could say [quote/blank!] if you wanted to match quoted blanks. :thinking:

It might be the inklings of a workable concept, or not.

Overall--however--I think DATATYPE! as antiform has proven a smart path, and that's unlikely to change.