Permissive Group Invisibility


One of my favorite design points has turned out to be "Invisibles". Initially conceived for achieving a "true" COMMENT, they have turned out to have far wider applicability than that.

They throw a wrench in some aspects of evaluation. If you think of the evaluator of moving in steps that always must produce a value, then they need to sneak into the surrounding evaluations...either before or after themselves. Most sneak in with the thing after themselves:

>> evaluate [print "One" elide print "Two" print "Three"]
== [elide print "Two" print "Three"]

>> evaluate [elide print "Two" print "Three"]
== []

In this way they are something like enfix functions, where 1 + 2 * 3 is just one "step".

It's a complexity tax to be paid in the design and creates some tough mechanics. It meant that DO/NEXT had to basically be redesigned as EVALUATE. But that's actually a good thing, and well worth it for all the cool things they enable.

But how should GROUP! interact with invisibles?

While messing around with some experimental debugger code, I made BREAKPOINT an invisible that doesn't take any parameters. This means you can write foo + breakpoint bar. It would break after foo evaluates as the first argument to +, before bar evaluates as the second arg to +.

But should you be able to say foo + (breakpoint) bar ?

The idea that a GROUP! could simply vaporize away if its contents are empty or invisible seems a bit unsettling. So these cases were errors:

>> 1 + () 2
** Error: + is missing its value2 argument

>> 1 + (comment "Hi") 2
** Error: + is missing its value2 argument

But how unsettling would it really be if those worked?

>> 1 + () 2
== 3

>> 1 + (comment "Hi") 2
== 3

I had said this was too weird to be justified "just for some comments". But when you think about a interesting multi-parameter breakpoint, wouldn't it be nice to put it where you want it and group it as well?

foo + (my-complex-breakpoint #arg1 #arg2) bar

You wouldn't be able to do this if grouping forced the production of a VOID! out of that emptiness. The same would be true for using DUMP today:

foo + (-- x y z) bar 

As invisibles become more an integral part of the system, it no longer seems like being weird for the sake of being weird.

Permissive Invisible Groups

An argument for groups not vaporizing is if you think of (...) as being a synonym for do [...]. If your code is coming from an unknown source, and it's a GROUP! may not know if it's going to vaporize or not. But...

  • that the actual scenario for GROUP!s? How often are unknown code bits being passed around as GROUP!, and then executed by being spliced into evaluation? Aren't blocks the currency for that?

  • why not just use DO on your groups if you're worried? If you do not know what kind of code is in a GROUP!, it must be coming by way of a variable. So the way you are getting the evaluator to run it is either DO (where this isn't an issue, because empty groups return void) or you've made an effort to splice it into a block so it evaluates inline. If you're forced to splice, why not splice compose [... do '(my-group) ...] if you don't want the ability to vaporize? It's a short word, and it liberates GROUP! to a distinct novel usage and behavior.

Of course, it's a thing you'd have to learn about GROUP!... that they aren't a synonym for DO of a BLOCK!. But it's a nuance... groups just group things, they don't synthesize any VOID!s or artificial values of their own.

The nuance is pleasing, and simple. I found that I had a preference for:

 case [
     something [...]
     (elide print "Got this far")
     something-else [...]
 ] opposed to:

 case [
     something [...]
     elide (print "Got this far")
     something-else [...]

(Though technically you don't really need a GROUP! at all in that case, but the use of groups is often a subjective thing.)

And if there isn't permissive group invisibility you won't get it any other way. I feel like there's multiple points in favor of this... expressivity, and a coherent story: "groups only function is to group things, they are ghostly, () 1 + () (comment "hi") 2 () is 3"

(Note: Because the GROUP!s are not enfix functions themselves, they are not entirely ghostly...they are "only as invisible as non-enfix invisibles are". This is to say that 1 () + 2 can't be the same as 1 + 2, because, the 1 doesn't see the + through the group!. It can't start evaluating it either to find out if it's empty and then have to back out of this is an error.)

(Also worth pointing out: if you COMPOSE a GROUP! which has null or splice an empty block via group...not even -invisible-, you get it vaporizing: compose [1 + (if false [<not-spliced>]) 2] => [1 + 2])

1 Like