In Rebol2, a refinement's value would be #[true] or #[none]:
rebol2>> show-me: func [/bar] [print mold bar]
rebol2>> show-me/bar
true ; actually LOGIC! #[true], not the WORD! true
rebol2>> foo
none ; actually a NONE! value, not the WORD! none
As a newbie, I was bothered by this from the get-go... because I couldn't test refinements with AND/OR. (they don't take #[none]) I hadn't drank the Kool-Aid that ANY/ALL were always a desirable substitute.
NOTE: I never drank the ANY/ALL supremacy Kool-Aid. Of course there are a lot of cool uses for them as larger scale control structures--and it's important to know how to use them for streamlining program flow. But for testing flags? The ability to do it infix and without delimiters can read much better in many places.
So Ren-C has adopted infix AND/OR that operate conditionally on ANY-VALUE! and return LOGIC! (they also short-circuit)
ren-c>> flag1: flag2: true ren-c>> if flag1 and flag2 [print "It allows WORD!s, this comes up often"] It allows WORD!s, this comes up often ren-c>> if all [flag1 flag2] [print "Not everyone loves this (e.g. not me)"] Not everyone loves this (e.g. not me) ren-c>> if (block? flag1) and (empty? flag1) [print "Short-circuit avoids error"] ; to do this, the second argument to AND/OR is quoted behind the scenes
There were countless debates over whether refinements should just be LOGIC! (as in Red), or if a used refinement should be the WORD! of the refinement (maybe useful for chaining?). For a time, it seemed murkier when Ren-C introduced NULL...although it all tied up rather nicely in the end!
Evolution Led Ren-C to Refinements As Their Own Arguments
-
Multiple-refinement arguments were a fringe feature, used almost nowhere. Primarily it just created an inconvenience for 1-argument refinements having to come up with some random name for the argument.
-
With the existence of antiform NULL, a single argument could represent the state of being used or not, along with the value itself.
-
The "used" state would be able to hold any non-antiform value (anything that can be put in a block)
-
NULL became a "branch inhibitor"--so just testing it with IF could cover most "is the refinement used" questions
-
All non-antiform values (and the antiform of ~okay~) eventually became branch triggers
- This solved all the issues of conflating unused refinements with used ones that were things like BLANK! or the WORD! of FALSE
-
-
It simplified the evaluator logic--removing the "refinements pick up every parameter after them" semantic.
- This paved the way for adding more arguments to functions after-the-fact, without worrying about them getting lumped in with the arguments of the existing last refinement.
But What About Refinements That Don't Take Arguments?
This question malingered along longer than I would have liked it to.
For some time it would give you back a WORD! that was the name of the refinement itself:
old-renc>> show-me: func [/bar] [print mold bar]
old-renc>> show-me/bar
bar
old-renc>> foo
== ~null~ ; anti
I'd advocated for this idea very early on:
-
Imagine if one function took
/only
and wrapped another function that also took an/only
-
If the wrapper got its ONLY variable for the refinement as the WORD!
only
...-
you could just say
inner-function/(only)
-
If you got the refinement, that acts like
inner-function/('only)
-
If you didn't get the refinement, that would act like
inner-function/(null)
-
-
-
If you squint your eyes and use your imagination a little, this might seem like a useful tool for chaining.
But the feature was a victim of Ren-C's other successes. It's much easier and more efficient to wrap functions with things like ADAPT, ENCLOSE, SPECIALIZE. The need to write this kind of "tunneling" function is rare: and refinement naming overlap is unlikely if the functions weren't somehow derived from each other.
(It also made building FRAME!s at a low-level more annoying...the value you put in the frames had to be contingent on the refinement name. And it forced path dispatch steps to permit NULLs, when it didn't allow it anywhere else. More complexity, for little benefit.)
"Wouldn't LOGIC! For Argless Refinements be... Logical?"
You might think so. But actually... no.
Even before the design of Flexible Logic (where FALSE can't be tested for by a raw IF), it didn't make sense.
An intuitive angle might be to consider how an argument-less refinement contrasts with a refinement that takes an actual LOGIC! argument.
foo: func [/mutate [logic!]] [...]
This refinement can be NULL (unspecified--e.g. the refinement was not used at all), or #[true] or #[false] when it is specified. There's three states.
But an argument-less refinement is really just "used or unused". So NULL and then a single truthy state... ideally a state that the system understands as meaning "I'm opting in".
The Modern Day: ~NULL~ or ~OKAY~
OKAY is a branch-triggering antiform that exists as the complement to NULL.
>> 10 > 20
== ~null~ ; anti
>> 10 < 20
== ~okay~ ; anti
It's the perfect choice for an argless refinement.
>> f: make frame! :some-function
>> f.argless-refinement: x = y ; slot will be null or okay