Which Antiforms Are In Use?

As an aid for myself, I made a summary table:

Type Antiform Usage Stable?
word! LOGIC! Just OKAY (truthy) or NULL (the only falsey state) :white_check_mark:
block! SPLICE! Values without surrounding container (empty is NONE) :white_check_mark:
fence! DATATYPE! Description of a value's type :white_check_mark:
blank! VOID! Panics on WORD! access, multi-step evaluations discard :cross_mark:
frame! ACTION! Trigger function execution from words :cross_mark:
tag! TRASH! Panics on WORD! access, console does not display :cross_mark:
group! PACK! Multi-returns from a function (empty is "HEAVY VOID") :cross_mark:
error! FAILURE! "Hot" failure signal, elevated to panic if not triaged :cross_mark:
3 Likes

Very useful!!! :star: :star: :star:

(and has been useful as a place for me to update it over time, to look at how it shapes up and makes sense!)

Do note that the %types.r table encodes the type/antiform information as well: Such as for splice:

block!      "list of elements that blocks evaluation unless EVAL is used"
~splice!~   "fragment of multiple values without a surrounding block"
            (payload1)
            [any-series? any-branch? any-sequencable?]

And for error:

error!        "context with id, arguments, and stack origin"
~failure!~:U  "error state that is escalated to a panic if not triaged"
              (payload1 payload2)
              [any-inert?]

(The :U means the antiform is "unstable".)

In any case, %types.r is a pretty good example of dialecting! As much as possible, it's good to use Rebol to do code generation for properties in the C (and also to put in the binary for HELP)!

%types.r has come a long way since R3-Alpha!

This is what it looked like in R3-Alpha:

A much better strategy has been implemented for specifying granular dispatch, so the last vestiges of weirdness of things like self string + f* file * are gone:

1 Like

This table has evolved significantly over time.

Here's a little trip down memory lane, and a few open questions to think about for the future.


SPLICE! started out as the antiform of BLOCK!...

  • but was quickly changed to the antiform of GROUP!
  • it finally changed back to the antiform of BLOCK! again :roll_eyes:

The theory of changing it to GROUP! had been that ~(...)~ helped make the sides of the container look "weak" or "missing", as if the act of SPREAD-ing had torn off the solidity of the block:

old-ren-c>> spread [d e f]
== ~(d e f)~  ; antiform 

old-ren-c>> append [a b c] ~(d e f)~
== [a b c d e f]

It also helped emphasize that whatever your container type had been, it was forgotten (since you SPREAD blocks more than GROUP!s, that lesson "jumped off the page" a little better).

This choice persisted longer than it should have, because a block-based PACK! seemed to make SET-BLOCK and PACK more related, e.g. [x y]: ~['1 '2]~.

But those arguments are much weaker than the argument that GROUP! suggests "live" evaluation, while BLOCK! suggests inertness.

You can easily see that [x y]: ~('1 '2)~ is a better match for PACK!, since it "evaluates" and drops the quoting level to get the actual value from the lifted forms. And using BLOCK! for splice helps emphasize that it is just a plain-ol stable value:

>> x: ~[a b c]~
== \~[a b c]~\  ; antiform (splice!)

>> x
== \~[a b c]~\  ; antiform splice

TRASH! started as Antiform TAG!... And Came Back To It

It looked good, and met with approval:

But I became bogged down with the problem of how to bridge the "minimal trash" for unsetting variables as antiform tilde with antiform tags:

>> x: ~  ; this was a trash assignment

>> y: ~<this was also a trash assignment>~

I couldn't connect the dots to where ~ could be a quasiform TAG!. And ~<>~ would be a quasiform WORD! (since <> is a WORD!). This meant the minimal TRASH! would be something like ~<#>~ So TRASH! was antiform RUNE! instead.

Things bounced around a bit until I realized ~ wasn't trash at all but the quasiform of the VOID! state. At which point, it became the lightest and most natural state to use to unset a variable. And if you had anything heavier to say, then you might as well write something out as to why you were trashing things.

So it came full circle and TRASH! was antiform TAG! again--and it took on a new interesting role of being able to serve as middle-of-line comment in evaluative contexts (exempt from discardability rules in evaluation).

VOID! and Empty-PACK! Have Had Many Names

(and BLANK! was once COMMA!, before it was rethought to only show the comma in its plain form, but not show the comma if it's decorated in any way...)

VOID, GHOST, NIHIL, NOTHING, and TRASH have circulated around more times than I care to count. :frowning: But there is a method to the madness of writing and iterating so much...

It was very tough to give up on calling antiform blank GHOST!. But as the vanishing behavior became more tightly controlled, it appeared ever-increasingly in non-vanishing roles. The prevalence in conversation made it a distraction to say "ghost" all the time -- when the vanishing aspect was diminishingly likely to be relevant to any given conversation.

So soul-searching led to the conclusion that VOID! was the right pick for the "light void" state, and empty pack being referred to simply as "heavy void" (or empty pack).

Many places are happy to just ask ANY-VOID? and accept either. Those who need the distinction often want to ask just VOID?...and I think it's a meaningful name for that.

The "ghost" terminology sticks around, but in a way that it only comes up when voids are actually vanishing. e.g. GHOST is a vanishable function returning void (an arity-0 COMMENT), and GHOSTLY is an operation that vanishes its argument when it is either VOID! or HEAVY VOID.


LOGIC! Hedged On More States (ANTIWORD!, KEYWORD!)

While OKAY and NULL were clearly useful, I was reticent to say that those were the only two WORD! antiforms.

It seemed like a waste... when the future might have other literate-looking states (~NaN~ for instance, for not-a-number)

This hedging meant that the type couldn't be narrowed to just a "semi-fundamental" LOGIC!, but needed to be the type-constraint LOGIC?. Dissatisfied with the name ANTIWORD! I fumbled around and tried calling the type "KEYWORD!"

With the rise of "Hot Potatoes" like ~(veto)~, ~(done)~, and ~(NaN)~... I realized that antiform WORD! wasn't being "wasted" by only holding ~null~ and ~okay~, but that this was a bold and solidifying choice.

By committing to what antiform WORD! would do for all time--and moving TRASH! to being unstable--we could say that all stable forms could be conditionally tested (e.g. by IF; it could take a non-^META argument and be sure that it wouldn't encounter an error in its conditional test).

Several things clicked into place from this decision, to close the door on other WORD! antiforms forever...


FAILURE! was once RAISED!, and once "ERROR!" itself

Originally I'd kept ERROR! as the inert form, and you would use raise e to make an antiform error out of a plain error...with it called RAISED!.

Raising started to sound too much like an exception--and antiform errors were being used freely as a sort of general control signal (e.g. to cue rollbacks in PARSE). So I went with fail e to produce the failure.

Needing a name other than RAISED!, I thought ERROR! sounded like a good name for the active form...and then the inactive form could just be OBJECT!. This was similar in spirit to Rebol2's concept of "Hot Errors".

However, it leaves a pretty big naming gap for what to call the inert objects that embody "error-like properties". I tried WARNING! which would then be promoted to antiform ERROR!... but this was fairly unsatisfying. Also, it seemed to run against the grain of every other language which inspected and manipulated ERROR! objects inertly.

This made me go back to calling the objects ERROR!, and the antiform a FAILURE!...which lines up particularly well with fail as a function.


I Don't Know If DATATYPE! Should Just Be TYPE!

The whole concept of types is rather fuzzy. But if we resolve that this really is the answer:

>> type of [a b c]
== \~{block!}~\  ; antiform (fence!) 

Then it probably should just be TYPE!.

The problem is that there's contention on what something like type of answers. e.g. if you make a BOOK! class that's object like, would it say:

>> type of accelerando-by-charles-stross
== \~{book!}~\  ; antiform (fence!) 

And would that BOOK! be bound to be able to find the class definition so you can ask questions of the class, etc.?

How would you know if it's an OBJECT!? Would you ask for the KIND ?

>> kind of accelerando-by-charles-stross
== \~{object!}~\  ; antiform (fence!) 

This has various ramifications, and I don't have the answers yet.

But Overall, Things Are Looking Pretty Great

The names and functions have only gotten more solid with time, and I think the persistence in caring has paid off.

1 Like