2022 Note: The introduction of "invisibles" was a watershed moment in the design of Ren-C. This is the thread where I explained having tried it out, and finding it to have a surprising number of applications.
As time went on, the idea of allowing you to freely comment out any range of code turned out to require too many compromises and contortions to be worthwhile. It also set up a precedent that would prove impossible to reproduce in mediums like PARSE. You can see the seeds of those problems in the ponderings of this thread!
Ultimately--for the sake of sanity--invisibility was limited to applying to "interstitial" evaluations only. isotopes are passed as function arguments if you try to use them in such positions, which winds up permitting most of the truly interesting behaviors...and dialects can smoothly integrate invisible intent into their own behaviors. Commenting out fully arbitrary ranges of code just has to be done another way!
But it all started here--kept for history!
So @MarkEye brought back up an idea that has crossed my mind every few months, about what it would take to make something that was truly "less than null". Some way of returning a complete-absence-of-information, including even information about the absence of a value. :-/
The most "obvious" application most people would jump to (which turns out--in fact--not to be so obvious at all) would be COMMENT. So imagine:
9 = do [1 + comment "a" comment "b" 2 * 3]
9 = do [1 comment "a" + comment "b" 2 * 3]
9 = do [1 + comment "a" comment "b" + 2 * 3]
Despite the simple appearance, there's a lot of holistic concerns of such a thing showing up in the Rebol ecology. Here's some:
"I've made an acid that can eat through anything..."
This can't come down to returning a new type of value (e.g. a COMMENT!). Because what would happen in your function when you said return make comment! ...
? It would be skipped.
How would you test for them? if comment? c [print "it's a comment"]
would turn into if comment? [print "it's a comment"]
Having it as a value type is not an option, so it would have to be some new character of the function definition itself.
You can't "GROUP! them" and keep their semantics
One might ask if there should be a difference between these two statements:
1 + comment "a" 2
1 + (comment "a") 2
COMMENT isn't a very motivating scenario, it's single-arity and it quotes. But what if you had a more complex operation in this class, with multiple arguments, including evaluated ones?
Rebol has used ()
as a "null generator" for a long time. But might it be revisited so that GROUP!s that wound containing no content--or just comments--to vaporize? That would mean all these were the same when running DO?
1 + comment "a" 2
1 + (comment "a") 2
1 + () 2
1 + 2
The short answer is No. The long answer is N: (o)
UPDATE: Later it was decided the real answer is actually much longer--it rules out these particular cases, while allowing groups to vaporize in interstitial positions. Hence you can group them and keep their (absence of) content, but you can't put those invisibled groups in some spots you could have put them without the group.
UPDATED UPDATE: In practice, having N: (...invisible...) 1 be equivalent to N: 1 does have meaningful applications. This is due to the expanded scope of invisibles beyond commenting, they truly have turned out to be useful for non-invasive debug constructs. The existence of
do
for always returning a value can help in scenarios where you are running generic code you don't understand and want to be sure it doesn't vaporize...such code is usually in a variable anyway! So the use case of GROUP!s being more "ghostly" is actually something that makes their character unique. See expanded reasoning.
1 key reason for using groups in the first place is to show the structure in a stream of varying arity. It provides an anchor to be able to say "that one GROUP! will turn into exactly one complete value, or it will error". So if o vaporized in the (o) above, should N be 1 now?
Pulling the rug out from under that with "zero or one values" would have to be very worth it. And it's very not. If an expression wants to be invisible and look convenient, make it a dialect and let it take a block:
1 i'm-invisible a <b> #c 'd + 2 ;-- don't define it like this
1 (i'm-invisible a <b> #c 'd) + 2 ;-- b/c this is void, not invisible
1 i'm-invisible [a <b> #c 'd] + 2 ;-- define it like this
Interaction with DO/NEXT...all invisible functions are effectively enfix
This is the biggest issue. Basically, a DO/NEXT cannot finish until it has consumed all these "invisible" expressions. Consider:
pos: _
do/next [1 + 2 comment "a" * 3] 'pos
For COMMENT to be truly "invisible", then that should act as 1 + 2 * 3
. And the only way it can do so is if when it reaches the comment "a"
that it eagerly continues processing, so it can find out if there's anything on the other side.
Furthermore, the only way to be actually "invisible" is not to damage the evaluator stack at all. You don't want the presence of the comment above to suddenly turn 1 + 2 * 3
into the semantics of 1 + (2 * 3)
. That means the comment needs to be dissolved right at the moment the 2 is evaluated, so it can be seen past.
Technically this is easy enough to do, but the results might surprise someone. Let's imagine you think it would be cool to modify something like the variable DUMP function to be one of these "invisibles". So you might write:
x: 1 + 2 dump [x] * 3
That seems pretty cool, and intuitive in this case that when you dump X it hasn't been assigned yet--the expression isn't completed. But would it be as intuitive if you saw:
x: 1 + 2
dump [x]
One might expect 3. But as the example above it shows, you can't get that invisible property that way. If you did, then DO/NEXT would treat that as two expressions.
Is it best to be honest and just call these enfix functions?
Rather than getting into the complex details of defining a new category of functions that are "kind of exactly like enfix functions", should we just say that's what they are? They're basically enfix functions which can pipe their left hand argument to the output in a transparent way. Says @MarkEye:
For the purposes of explication, can one consider COMMENT to be a tight infix operator that "returns" its left-hand side? (haha and its left-hand side is allowed to be empty!) Example:
do/next [comment "thrillsville"]
should behave exactly likedo/next []
, shouldn't it?
There are a few technical challenges to implementing true transparency in this way, given that there is no END! datatype (yet behaviors can be distinguished internally to the evaluator between end and null). It could be worked past with some kind of return/proxy
function that you just point at the argument you want to telegraph, and the evaluator takes care of it.
We don't want to increase the number of parts in the box unnecessarily, so piggybacking on ENFIX may be okay. And also, making it a generic enfix mechanism means someone could design such an abstraction with non-tight semantics as well (if they're okay with non-total-invisibility).
But it may be "weird", and surprise someone who types HELP COMMENT and wonders why it's not the "naive" form. Or as @MarkEye says it "explicates" the situation. Hiding the "latching" behavior on the previous result would only obscure the process.
Thoughts??