True, False, On, Off, Yes, No...?

UPDATE: This thread led to the conclusion to not treat ON, OFF, YES, NO as synonyms for TRUE and FALSE, but instead rethink the paradigm as what we are calling "Flexible Logic". In this system, plain WORD!s are used to represent boolean states that indiscriminately take branches in things like IF...and NULL is the only state that inhibits branching.


Historical Rebol does this definition:

on:  true
off: false
yes: true
no:  false

And Red carried it forward. Whether it's good or bad, I don't know (my instinct is to say bad). I do know that it's lossy.

red>> on
== true  ; not a word, but a misleading display of #[true]

Ren-C has the opportunity to do something different.

>> on: ~on~
== ~on~  ; anti

>> off: ~off~
== ~off~  ; anti

>> yes: ~yes~
== ~yes~  ; anti

>> no: ~no~
== ~no~  ; anti

And then these WORD! antiforms could be treated as truthy and falsey as appropriate. Negation could preserve the form:

>> not yes
== ~no~ ; anti

Does it have value? :thinking: Well, my instincts are that if there is any value to have in defining these, that value arises from having them be differently-typed logic-like things. e.g. a function that took ON and OFF would not take YES and NO.

>> security-system on
The security system is now activated.

>> security-system false
** Error: What are you talking about?

Does This Buy You Anything?

What you get here is a little bit of friction on the callsite if your decision comes direct from math:

>> security-system (user = 'Ron)
** Error: security-system requires TOGGLE! as enable argument, not ~true~

So you have to write something more laborious:

>> security-system (either user = 'Ron [on] [off])
The security system is now activated.

At which point you might ask what the advantage is here over just using the words ON and OFF.

The advantages I see are:

  • Callsite doesn't have to quote ON and OFF like (either user = 'Ron ['on] ['off])

  • Once arguments are received, callee can use conditionals like IF and CASE and such directly:

    if enable [...]
    

    ...instead of...

    if enable = 'on [...]
    
  • You preserve the semantically correct word in the antiform, so you can see it in that form in debug output, and generalized print routines can extract them, e.g. TO WORD! gets you the word you want instead of just TRUE and FALSE

  • You're letting people guide the callsites to use clearer terms with some amount of enforcement, instead of permitting yes and true where ON is intended

  • FWIW the words are shorter. false is 5 letters while no is just 2

How Valuable Is It?

Infinitely moreso than just defining them to true and false. (Mostly because any amount is more useful than zero.)

I started this out as devil's advocacy, but I think I've actually talked myself into it. It's pretty cool.

Naming The Types

Uh, YES-NO? and ON-OFF? kind of suck, like calling logic TRUE-FALSE? would suck. But they're clear.

I asked Claude for thoughts:

These are some interesting ideas for expanding the concept of boolean types. Let's break down the potential names and rationales:

  1. BOOLEAN! or LOGIC! for TRUE/FALSE

    • This is the standard name, representing traditional boolean logic.
  2. SWITCH! or TOGGLE! for ON/OFF

    • These names capture the idea of a binary state that can be flipped.

    • SWITCH! might be slightly more intuitive as it's commonly used in programming and everyday language.

  3. CONFIRM! or RESPONSE! for YES/NO

    • CONFIRM! does indeed capture the essence of affirmation or negation.

    • RESPONSE! could work too, as it implies an answer to a question.

Your suggestion of YES-NO! and ON-OFF! as type names is also valid, though they might be less conventional in programming language design.

The key aspect of your proposal is that these types, while similar in their binary nature, are distinct and not interchangeable. This could lead to more precise and context-specific code, but it might also increase complexity.

I like [TOGGLE?] and [CONFIRM?]

But your type check can use quasiforms as a proxy for antiforms, e.g. [~yes~ ~no~], and that may be about as clear.

1 Like

Part of my negative impression of it comes from Rebmake, where Shixin felt like he wanted to support the natural words, but could not require it. He wound up with things like this:

append flags spread switch rigorous [
    true 'yes 'on 'true [
       ...
    ]
    false 'no 'off 'false [
        ...
    ]
]

The situation here is that the configuration data is sometimes coming from evaluative contexts (a config file that is running code assigning objects to members, (rigorous: yes) and sometimes non-evaluative contexts (e.g. a string on the command line like "rigorous: true") If the context was evaluative then the nuance between yes/no/on/off/true/false is lost. If it's non-evaluative you still have the word.

My feeling would be that the config file should be written to the same convention to bring it in sync with what you get from the command line, so (rigorous: 'yes). Getting all the code on the same page is worth using the apostrophe for a "dialected" word. It's the better tradeoff.

But that aside... I didn't like the fact that all the words had to be tolerated. It just makes a mess.

So this is where part of my bias that they shouldn't be synonyms comes from.

I am liking the distinction of ON and OFF. It communicates more about what the toggle is doing.

symbols: on

...as opposed to...

symbols: true

That has a nuance to it that I think is valuable. And it feels easy and natural to me to tell which things are ON/OFF vs. TRUE/FALSE. recycle false vs. recycle off has a clear winner.

I don't have the same confidence about YES/NO vs. TRUE/FALSE. Having lived in a world of just true and false for so long I honestly can't tell if they're actually a good default.

>> 1 > 2
== ~no~  ; anti

>> 1 < 2
== ~yes~  ; anti

That seems pretty natural. But true and false are rather ingrained.

Anyway, it is hazy to me where YES and NO have enough benefit to justify not just being able to use boolean logic expressions to pass the value. But I definitely see the value of ON and OFF.

I'll continue to experiment and see how my feelings evolve.

The one point here though, is that switches tend to expand. Like new modes get added, to where you might expect there to be not just ON and OFF but some distinctions like HIBERNATING or whatever.

And when the instructions expand, the new instructions have to be quoted as words, for recycle 'infrequent. Then you have the issue of just ON and OFF standing out as being able to be not quoted, and it's inconsistent.

This is something I wrote about a long time ago (2017):

So my fear is that once you go into something semantic like ON and OFF, that jump into the semantic realm may be just the beginning. And letting you not quote the words may in the long run lead to having an inconsistent interface when your semantics expand.

Back To Where This Line Of Thinking Has Always Led...

...ever since 2017 (and before), which is that there is no LOGIC! type, and you instead say:

some-flag: 'true

if true? some-flag [...]

And then what gets returned by TRUE? is either NULL or some canon "you should take the branch" value (I'll avoid calling that "truthy"). It could be rigged so that TRUE? and FALSE? accept only TRUE and FALSE words.

>> true? 'true
== #  ; or whatever...as long as it's not ~null~ antiform (or ~NaN~ antiform...)

>> true? 'false
== ~null~  ; anti

>> true? 10
** Error: TRUE? and FALSE? tests only work on TRUE and FALSE words

>> logic? 'false
== #

>> logic? 10
== ~null~  ; anti

Actually...now that NOTHING is truthy, I can see definite advantages to making it the canon "run the branch" value. This means it's good enough for comparisons that aren't being stored in a value:

>> 10 < 20

>> if 10 < 20 [print "Good enough for this."]
Good enough for this.

But if you store it in a variable, it unsets it. That's a good way of saying you didn't triage it into some sort of reified state, which would be friendly to rendering and putting in blocks etc.

>> flag: 10 < 20

>> if flag [print "You triaged it into a meaningful reified state"]
** Error: Flag is ~ antiform (unset variable)

So instead you'd have to write:

>> flag: either 10 < 20 ['true] ['false]
== true

>> flag: to-logic 10 < 20
== true

>> flag: boolean 10 < 20  ; fewer characters, no hyphen
== true 

>> flag: bool 10 < 20  ; abbreviation for those who want it
== true

Bummer here is still that if you leave off the TRUE? test in if true? then your FALSE state will run the branch, because all reified values are non-null. But with the interplay of LOGIC!s sometimes being words (or sometimes being quasiforms) you were always at risk of that. This just bites the bullet and tells you to learn the new and consistent way.

If you're starting from scratch learning the language you can be taught that IF just tests for non-nullness. That's all it does. If you know that's all it does--and it's ingrained to you from the beginning--then you will know that you have to say if blank? or if trash? or if true? or if false? etc. etc. You won't expect it to test flags.

Furthermore: if flag now has a distinct meaning. It means "if the flag has been assigned a value--including either true or false". That is useful in its own right if one has learned it this way, and if you read it simply as "if flag is not null" it is actually quite obvious. When you're trained to read it that way, that's what you'll see it as.

A New Idea. But a Learnable One.

It appears to be the better long-tail path for a Rebol in which we've seen the tension between words and non-reifyable states (or undesirably ugly reifyable states) causing catastrophic consequences in the code.

  • It applies absolutely everywhere else (including BLANK? now, as it too causes branches to run).

  • There'd never be a confusion between the "fetched state" of a LOGIC! and the WORD! of the LOGIC because the WORD! is the logic.

  • The new "run the branch" signaling of NOTHING turns the arbitrary choice of what logic expressions return for true into something non-arbitrary. It serves a purpose in calling attention to non-triaged results being used as variables.

  • if flag becomes a new and interesting test, to help discern an unassigned flag (one that is null) from one that has been assigned a value (either TRUE or FALSE, or whatever else).

    • Someone learning Ren-C from scratch would learn that meaning and read it for that meaning, not expecting it to test for true or false.
  • Rendering the TRUE and FALSE "enum states" becomes as natural as any other word, and the states can expand without requiring a difference in handling.

  • You can put the currency we use for representing logic into a BLOCK!

  • With TRUE and FALSE being themselves "take the branch" signals, they can both drop out of things like ANY or ALL verbatim.

    flag: update all [
         threshold > maximum
         even? number-of-variants
         'false
    ]  ; result could never be false before, always null
    
  • We can dismiss with this question of ON OFF YES NO antiforms. All of them (as well as TRUE and FALSE) would be undefined.

And if people get exposed to things like either 10 < 20 ['true] ['false] early on, it might train their brain to start thinking about all the possibilities Rebol has to offer them for expression. It's a pattern with a smooth slope toward getting more out of the representation and out of the language.

My 2017-Self May Have Known More Than My 2022-Self

The lingering feeling now seems spot on.

I think this is the answer.

I'll point out that we now type check quoted literal things.

machine-control: func [mode ['on 'off]] [
    switch mode [
         'on [print "Turning machine on"]
         'off [print "Turning machine off"]
    ]
]

>> machine-control 'explode
** Error: machine control requires mode as ['on 'off]

We still might want a TOGGLE? type constraint, and functions ON? and OFF? which check to make sure you're only testing something that is the WORD! of ON or OFF... same for YES? and NO?

Benefit Of The New Idea In Practice

Poking at Rebmake still more, I ran into this:

assert [logic? user-config.odbc-requires-ltdl]
compose [
    %odbc (if user-config.odbc-requires-ltdl [%ltdl])
]

Here we are trying to do a sanity check, because if there's some stray value in the field we don't want to assume it is true or false.

But guess what...

compose [
    %odbc (if yes? user-config.odbc-requires-ltdl [%ltdl])
]

You get the check for free. YES? and NO? only respond for the words YES and NO, and error on all other values.

So here's a layer of validation in the code. And I think it's neat that you can use YES and NO if it's appropriate instead of TRUE and FALSE. Here I think it's better--and we're just sort of brainwashed into using TRUE and FALSE in places they aren't actually the right words.

This also means that you can see when things are logical, saving you from needing to name the variable with a ? in it. (I always hated it when people did that.)

if project.generated? [print "Sometimes people named variables like this"]

if yes? project.generated [print "You keep the clarity, plus a bonus typecheck!"]

Bootstrap Implementation

; Note: bootstrap EXE doesn't have closure, can't refer to WANT/NAME/DONT directly.
; Must compose values into the generated function body.
;
wordtester: enfix func3 ['name [set-word!] want [word!] dont [word!]] [
    set name func3 [x] compose3/deep [
        case [
            :x = the (want) [true]  ; bootstrap can't do :x = '(want)
            :x = the (dont) [false]
        ]
        fail [to word! the (name) "expects only" mold compose3 [(want) (dont)]]
    ]
]

true?: wordtester 'true 'false
false?: wordtester 'false 'true
on?: wordtester 'on 'off
off?: wordtester 'off 'on
yes?: wordtester 'yes 'no
no?: wordtester 'no 'yes

So NOT will only flip the null and undefined status.

That raises the question of what you'd use to flip a TRUE to a FALSE or a FALSE to a TRUE (or a YES to a NO, etc.)

You could write something like:

flag: bool not true? flag    ; a bit convoluted
flag: bool false? flag       ; a little shorter, but less obviously a NOT

Haven't seen a need for it yet, though I only just started tinkering. Most of the places that use NOT are just inside a test, and not saving the result into a variable.

Further Observations

NOT needs to reverse the NULL-ness of what it is given. So anything that isn't NULL you pass it needs to give back NULL. And NULL needs to give back something that will cause branches to be taken (e.g. NOTHING, in my current formulation).

So you don't use NOT with TRUE/FALSE/ON/OFF/YES/NO. You use FALSE?, OFF?, or NO?

With NO? it's even the same number of characters as NOT:

 if not var [...]
 if no? var [...]

I am finding that when given the freedom to choose, YES and NO are a preferred choice to TRUE and FALSE for many things. Kind of not seeing a lot of places that I actually want to use TRUE and FALSE.

In about exactly 24 hours (including about 8 hours of sleep), I managed to go from having the fleeting thought of ~on~ and ~off~ and ~yes~ and ~no~ isotopes, to implementing the long-wished-for WORD!s-for-logical-states...and having a non-trivially booting system (can run TLS and HTTP, etc.)

The fact that I could do it that fast tells you something--Rebol doesn't really use a whole lot of variables that are true and false. It's rare to pass them as arguments to functions, because parameter-less refinements are more the way to do that.

It works better than what we had before. And the ways in which it is "weak" are really just bringing true and false into the realm of how you deal with every other thing in the language. Trying to give an exceptional experience of using LOGIC! has really just wound up as an experience in creating a lot of exceptions. This is a better path, that gets people in the rhythm of thinking with words--not the ideas they are used to from other languages.

Some Spots Showcase The Benefit

Consider a case where you are trying to discern a refinement that has been supplied, and would hold a logic if so:

===: func [
    return: [nihil?]
    'remarks [element? <variadic>]
    /visibility [logic?]
    <static> showing (false)
][
    if not null? visibility [showing: visibility, return nihil]

    if showing [...]
]

You have to specifically say if not null? visibility because you can't just use if visibility to ask if the refinement had been supplied. When you consider NULL to be the only branch inhibiting state, the question is natural... if visibility means exactly that: are you non-null.

===: func [
    return: [nihil?]
    'remarks [element? <variadic>]
    /visibility ['yes 'no]
    <static> showing ('no)
][
    if visibility [showing: visibility, return nihil]

    if yes? showing [...]
]

To me that paints a sort of picture of why we are better off without the likes of #[true] and #[false]. When you get away from the mental chains of thinking you have to be limited to those words, you are getting into the mindset of how to do more with the language.

Some while [true] Type Places Need A Different Answer

true is no longer defined, so you can't do that (unless you define true yourself for some reason).

Although technically you could just change that to while ['true] and it would work, that's rather misleading, because while ['false] would do the same thing. Not a good way to teach people how to think about this.

You could say while [true? 'true] and be accurate, but that hardly seems like the best choice here.

while [#] isn't the worst choice... the zero ASCII character literal is compact--looks solid. It's what no-argument refinements currently use to be branch-triggering. Certainly there's nothing overtly wrong with it, besides being "symboly"... to where someone might look at that and go "what does that mean?"

More talkative choices like while [#forever] communicate more without costing any more. That's a short enough character sequence to fit in a cell. You could say while [<forever>] with a TAG! that always does a string allocation (because it's mutable), but it's still a trivial cost when only one copy of the code exists. And it doesn't have to dereference the string node to know it's not a null, so same runtime property as anything else. If you think it looks better I wouldn't worry about the extra 8 platform pointers it costs.

Similar arguments apply to how some people (who don't like generalized ELSE) are accustomed to writing:

case [
    condition1 [...]
    condition2 [...]
    true [...]
]

You wouldn't want to change that to 'true for the reasons I've already mentioned (e.g. that 'false would do exactly the same thing).

A literate choice would probably be mentioning how this is some kind of default.

case [
    condition1 [...]
    condition2 [...]
    #default [...]
]

Again, the "default" fits entirely into an immutable character sequence cell such as that, so it's very cheap.

Anyway: my point is that 'true now represents a particularly poor and counter-educational choice for arbitrary truthy things. There's an infinite number to choose from. Just don't choose that.

Turns Out I'm a 'yes Man

For practically everywhere that was using true and false, I think yes and no read better.

is-dynamic: false
=>
is-dynamic: 'no

And they're shorter. Plus if you said if not is-dynamic before, you can say if no? is-dynamic now.

I just wish there was a better name for to-yes-no, if you want convert values that are NULL to no and every other non-void thing to yes.

I am very tempted to reclaim DID and DIDN'T for this. "Did you find it?" -> yes, no

>> did find [a b c] 'b
== yes

>> did find [a b c] 'e
== no

>> didn't find [a b c] 'e
== yes

Predictably Most Questionable: NOTHING for "Branch Trigger"

The likely best other choice would be the zero ASCII character representation, #. It's already used for no-argument refinements when they are supplied.

 >> 10 < 20
 == #

 >> 10 > 20
 == ~null~  ; anti

If we went with this, it has the seeming appealing property of letting you store the results of a logical expression in a variable, and then use it without having to invoke YES? or TRUE? or OFF? or anything like that:

 >> var1: 10 < 20
 >> var2: 10 > 20

 >> if var1 [print "This works."]
 This works.

 >> if not var2 [print "So does this.  What's wrong with it?"]
 So does this.  What's wrong with it?

What's wrong is that I think this would encourage people to be representationally lazy, and it would slip the meaning of NULL into a haze.

You'd stop really knowing what NULL means. It ceases to be "unassigned thing that you don't really want to be passing around as a parameter". People would start passing it around as if it was some kind of measurement. It would become a currency for booleans, and I don't think it should be doing that.

That's why I speak of this "forced triage": I want people to be turning it into the right words for the problem, not leave it as null. And it seems to me the best way to do that is to pick an ornery truthy state.

"Okay, let's say we buy into that: having people assigning "logic" expressions to variables getting them unset half the time is actually a call to action to make better code, and keep NULL living to its full potential. What are the downsides?"

Returning "Logic" (NOTHING or NULL) is a Bit Weird

 foo?: func [return: [logic?] ...] [
     if whatever blah whatever [
         return ~
     ]
     return null
 ]

That doesn't look a whole lot like a function that's dealing in LOGIC?.

We're in a bit of a pickle in a sense, because we have to find some way of saying "return branch trigger" or "return branch inhibitor" without using the words true or false. :grimacing:

The first idea I had was to make something that we might consider useful for other reasons: LOGICAL, a routine which converts 0 to NULL and all other integers to NOTHING.

 foo?: func [return: [logic?] ...] [
     if whatever blah whatever [
         return logical 1  ; e.g. NOTHING
     ]
     return logical 0  ; e.g. NULL
 ]

It's not a useless function, and it dodges words we don't want to use. Doesn't seem like a terrible idea.

Of course, a lot of the time you're not generating the states. You're just passing on the result of another function (e.g. writing a routine like even? in terms of odd?, and you can just say return not odd? number without having to generate anything.)

Forces Functions Like REMOVE-EACH To Pick A Keyword

The function REMOVE-EACH will remove the items you say to remove.

 red>> remove-each num [1 2 3 4] [even? num]
 == [1 3]

Red considers UNSET! (nothing) to be truthy, and REMOVE-EACH only cares if your branch comes out truthy (not specifically true or false). So if your block of code ends with something like PRINT, that winds up being a removal instruction:

red>> remove-each num [1 2 3 4] [print num]
1
2
3
4
== []

I find that a bit too liberal and easy to make mistakes. I required your loop body to evaluate to a LOGIC! (remove if true, keep if false), NULL (keep), or VOID (keep).

If I were to carry that forward...it would mean taking NOTHING, NULL, or VOID. But there are a -lot- of ways to make NOTHING (e.g. PRINT).

Does this mean REMOVE-EACH needs to force you to pick a word form of logic?

>> remove-each num [1 2 3 4] [even? num]
** Error: REMOVE-EACH expects TRUE or FALSE (or VOID to skip)

>> remove-each num [1 2 3 4] [bool even? num]
1
2
3
4
== []

I'd almost rather it expect the body to evaluate to KEEP or REMOVE, but you lose some elegance. Though being able to skip voids might work out:

>> remove-each num [1 2 3 4] [if even? num ['remove]]
1
2
3
4
== []

So we're in a situation where I'm not uncomfortable with the canon branch-triggering state being the same state that unsets variables. That's a feature. The thing I'm less pleased about is if it's being generated by other things (like eval [] or case [1 < 2 []] or print [...])

# May Be Better Than Too Much Prescriptiveness

It would be consistent with unused refinement arguments. You would be able to use them directly:

>> f: make frame! :append

>> f.line: 1 > 2
== ~null~  ; anti  (valid state for the /LINE no-arg refinement)

>> f.line: 1 < 2
== #  ; valid state for the /LINE no-arg refinement

(It used to be that we had to worry about whether to convert TRUE and FALSE automatically, but now we would not do that--because those are WORD!s.)

It would allow you to save conditional expression tests in variables, even though you probably shouldn't do that very often.

It would mean that REMOVE-EACH could require #, NULL, or VOID.

But one thing I don't like about it is that you can put it in blocks. I'd rather whatever this truthy state is be an antiform. That's probably a good property for the used refinement state as well.

What's a good short word that's the opposite of NULL that could be branch triggering that isn't TRUE? ~used~ makes sense in the context of refinements, but does it make sense for comparisons in general?

>> 1 > 2
== ~null~  ; anti

>> 1 < 2
== ~used~  ; anti

There's ~okay~

>> 1 > 2
== ~null~  ; anti

>> 1 < 2
== ~okay~  ; anti

Using that for refinements doesn't necessarily seem like an improvement.

>> f.line: if some-condition [okay]

>> f.line: if some-condition [~used~]

>> f.line: if some-condition [apex]  ; apex: ~apex~

Apex is actually pretty cool. But certain to look strange.

>> 1 > 2
== ~null~  ; anti

>> 1 < 2
== ~apex~  ; anti

The somewhat pejorative nature of TRASH makes me a bit reluctant to use it.

>> 1 > 2
== ~null~  ; anti

>> 1 < 2
== ~

But also, you need to quote it. I think this all just points to # being the most sensible choice, even though it's not an antiform. You can't have everything.

NULL Complement Aside, The Rest Is A Winner

I am happy with using the words and the TRUE? YES? tests...and having the new understanding of NULL being the only branch-inhibitor (besides, probably, NaN).

I find it very interesting to see that Ren-C has converged on the same solution as Lisp: a special value (~ or '()) for falsy, anything else for truthy. Strong evidence that this is on the right track!

1 Like

Isotopes do make it measurably distinct...

And to competently play the hand I've dealt myself, NULL's complement in conditional logic (e.g. returned by comparisons or functions like EVEN?)--must be an antiform.

Having struggled with names, I think the best choice is probably OKAY.

>> 10 > 20
== ~null~  ; anti

>> 10 < 20
== ~okay~  ; anti

There are tradeoffs to anything you'd put here. It's an antiform whose sole reason for existing is to not be null, and not be legal as a List element.

You could call it ~not-null~. :face_with_diagonal_mouth: Claude.ai was suggesting ~value~ (or ~valu~ if I wanted 4 letters--which I prefer--but not enough to be illiterate to get it). It goes with that vein of "I am just a generic value that a variable can hold, and there's nothing else interesting about me except that I'm not null". It's not meritless, but among its problems are that putting it into a word (like value) competes with a very important variable name...as well as that in the vernacular, an ANY-VALUE? is anything you can put into a variable, and ~null~ antiforms are in that set.

Trying to use a non-word for it has a host of problems. ~()~ and ~[]~ etc. are taken for actual purposes (splicing, parameter packs). I don't want SIGIL! antiforms, and if ~#~ were permitted that would probably require permitting ~#a~ and everything else. And if you use a non-word, you'd still have to name it to talk about it in discussion...or to put it in a WORD! so you didn't have to put the tildes on at the callsites where you used it. So you don't really buy yourself anything using a symbol.

On balance, OKAY is just... okay. It's like "yeah, all right, here's your non-null thing". There are places where it makes sense, like in the tests. There it is expected that the canon branch trigger to drop out, or the test fails.

; Note that BLOCK!s in the test dialect isolate the code into a module, so
; local definitions (or redefinitions of library words) are isolated into
; that block of tests! 😎
[
    (var: 2, okay)  ; just saying `var: 2` the 2 drops out, test fails
    (even? var + 2)
    (odd? var + 1)
    (for-each x [2 4 6] [assert [even? x]], okay)
    ~expect-arg~ !! (even? <banana>)
]
; Also: I like having the QUASI-WORD! choice there for dialecting error IDs.
; But I believe it should be illegal to EVAL/UNMETA those to get antiforms
; unless they're in the system-vetted list of word antiforms.

OKAY works pretty well there.

It's a little strange that "used" refinements would be just "okay"

>> foo: func [:refine] [probe refine]  ; in the soon-to-be notation

>> foo
== ~null~  ; anti

>> foo:refine
== ~okay~  ; anti

But I don't know if that's any stranger than anything else. So far we've been getting # as the "arbitrary truthy thing". I can't say how that's better--but I can articulate how it is worse (e.g. having various dialected meanings, appending zero bytes to binaries, matching # in block parses, COMPOSE-ing into slots without complaint, etc. etc.)

Anyway, I think I'm comfortable with it. An added advantage is that there's an abbreviated form (ok, ok?)... so you can say frame.refinement: ok or a test like (var: 2, ok), which is close to as brief as # while being more meaningful and getting all those sweet, sweet antiform benefits. :+1:

I'm a little less comfortable with the idea of people casually embracing ~okay~ and ~null~ for their boolean-like variables just to get the benefit of being able to test with IF directly. But if the system can make that choice for argument-less refinements, its pretty hypocritical to say you should never do it. Prescriptivism has limits.