The Long-Awaited Death of /ONLY

As of July 2022, /ONLY is no longer a refinement available on APPEND, INSERT, CHANGE, FIND, SELECT, COMPOSE, or similar functions. It only appears in the Redbol module through emulation.

"AS-IS" Semantics Are Default

It's easy to empathize with the historical Rebol idea of "splice blocks by default", if you only look at examples where the code is fully literal:

append files [%foo.r %bar.r]
code: compose [... keep [sum: sum + x] ...]

But problems immediately strike when you start using variables that may-or-may-not-be blocks. You can't rely on any invariants, and this leads to broken code:

>> block: [#a {b} [c d e] %f]

>> pick block 3
== [c d e]

>> find block pick block 3
== ~null~  ; anti

Time and experience has proven that "as-is" semantics are the safest and clearest default:

>> append [a b c] [d e]
== [a b c [d e]]

Ren-C offers a new tool for "spliced" semantics...which is to pass a GROUP! antiform ("quoting level -1"). SPREAD is the first function offered which produces them:

>> spread [d e]
== ~(d e)~  ; anti

>> append [a b c] spread [d e]
== [a b c d e]

But there's nothing particularly special about SPREAD, and there will be many other options for defining functions that may-or-may-not return group antiforms...which have more parameterization and nuance.

Making The Value Carry Splicing Intent Brings Systemic Good

The benefits are everywhere, with safer defaults and clear choices...for instance in REPLACE:

>> replace/all [[a b] a b a b] [a b] [c d e]
== [[c d e] a b a b] 

>> replace/all [[a b] a b a b] spread [a b] [c d e]
== [[a b] [c d e] [c d e]]

>> replace/all [[a b] a b a b] [a b] spread [c d e]
== [c d e a b a b]

>> replace/all [[a b] a b a b] spread [a b] spread [c d e]
== [[a b] c d e c d e]

Branching code can make decisions to splice-or-not-splice on a case by case basis:

>> code: [<splice> [a b] <no-splice> [c d] <no-splice> 'x]

>> map-each [instruction item] code [
       switch instruction [
            <splice> [spread item]
            <no-splice> [item]
            fail ["Bad instruction:" instruction]
       ]
   ]
== [a b [c d] 'x]

And COMPOSE can have some slots that splice and others that do not, within the same operation:

>> data: [a b]

>> compose [spliced (spread data) non-spliced (data)]
== [spliced a b non-spliced [a b]]

(COMPOSE is a case where not splicing by default is glaringly obvious as the right choice for a generic operation.)

Antiform Parameter Conventions Should Be Used Sparingly!

Routines that do not intend to react to an antiform should generally not take them as parameters.

As a good example, REDUCE might seem like the kind of thing that could act on group antiforms:

>> append [a b c] reduce spread [1 + 2 3 + 4]
== [a b c 3 7]

But we want to avoid this temptation, to stop the "spread" of antiforms to touching parts of the system that should not be concerned with them. So exercise restraint here...and shift the burden on the calling code to reshape itself so that antiforms only exist at the points that are very close to the calls they affect:

>> append [a b c] spread reduce [1 + 2 3 + 4]
== [a b c 3 7]

Good Riddance /ONLY

The elimination of /ONLY from the semantic model, and to have it not contaminate the user's heads, means that people can learn more generic tools that work in more contexts.

Its appearance in Ren-C will be limited to the Redbol compatibility module.

4 Likes

Can this be addressed in how SPREAD blocks are evaluated? A variation on lit-bit decay :grimacing:.

>> spread [a b c]
== ~(a b c)~  ; anti

>> thing: spread [a b c]
== ~(a b c)~  ; anti

>> thing
== [a b c]  ; on second evaluation, decay

>> append [] thing: spread [a b c]
== [[a b c]]  ; ^^ is second evaluation, => decay

Probably not, but would be nice to ditch the assignation as soon as possible as it's something of a hidden attribute.

We can debate whether a plain WORD! access of a variable containing a group antiform should work or if it should require a special accessor (like GET-WORD! or GET/ANY).

And we can ask whether you should be able to store group antiforms in words at all, or if that just errors.

>> thing: spread [a b c]
** Error: Can't store group antiforms in WORD!s (use meta)

>> thing: meta spread [a b c]
== ~(a b c)~

But empirically it seems like being able to treat these as regular variables has enough advantages to allow it. We'll have to see.

One issue here is that SPREAD things forget about the type of the input that was spread. If you SPREAD a GROUP!, you get an isotopic group... an if you SPREAD a BLOCK!, you get an antiform group as well. Other operators that are willing to split up PATH! and TUPLE! would make antiform groups out of them too.

(The eye-catching "information was lost" aspect was part of what motivated choosing groups instead of blocks for the splice antiform type...since blocks will be the most common input, turning them into a group is a good way of showing it's lossy.)

So that kind of would guide us away from thinking about decay here.