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 sympathize 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

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 BLOCK! isotope ("quoting level -1"). SPREAD is the first function offered which produces them:

>> 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 block isotopes...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.)

BLOCK! Isotopes Are Special ("unfriendly" :japanese_goblin: )

Normal parameters cannot accept them.

>> normal-function: func [value] [print ["Normal got:" mold value]]

>> normal-function spread [d e]
** Error: NORMAL-FUNCTION can't take a BLOCK! isotope as its VALUE argument

The way that you indicate you can take an isotope is using a ^META parameter.

But the convention with meta parameters is they will always be "one quote level higher" than the thing that they represent.

>> meta-function: func [^value] [print ["Meta got:" mold value]]

>> meta-function [d e]
Meta got: '[d e]  ; one quote level above 0 for unquoted input

>> meta-function first [''[d e]]
Meta got: '''[d e]  ; three quote levels above 0 for double-quoted input

>> meta-function spread [d e]
Meta got: [d e]  ; no quotes for isotopic input

Using this detection mechanism, you can write your own routines that react specially to isotopes.

Isotope Parameter Conventions Should Be Used Sparingly!

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

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

>> 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 isotopes 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 isotopes 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:. Will use #"`" to denote a -1 quoted block (for presentation purposes, not a proposition):

>> spread [a b c]
== `[a b c]

>> thing: spread [a b c]
== `[a b c]

>> 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

I do not believe in decaying them, so this is what you would get

>> spread [a b c]
== [a b c]  ; isotope

>> thing: spread [a b c]
== [a b c]  ; isotope

>> thing
** Error: THING is an isotope, you must use ^META operations

>> ^thing  ; ^META access adds one level of quote, removing isotope status
== [a b c]

>> get/any 'thing  ; should likely be called get/isotope now
== [a b c]  ; isotope

All this is new, so the thinking is going to naturally get shuffled around. It's a complex puzzle with a lot of pieces.

But...it may be that the existing rules are right, and isotopes don't really have any representation at all.

You only know they exist by virtue of ^META'ing them and seeing something unquoted...or the errors you get when you try to access them (which tell you you have an isotope, and show you the value un-isotopified).

This is how things work today (which is why you get the little ; isotope comment stuck after the BAD-WORD!s that are in isotope forms). But I thought that maybe it would be possible to generalize this in formatting. But trying to reason about it in any way that isotopes are willing to "mold" doesn't cohere.

I'm definitely leaning toward a new meaning for GET-XXX! things, which builds in an UNMETA. This is unfortunate for those who liked :[...] meaning "reduce block". But on the plus side, it would give us a new shorthand:

>> :[d e]
== [d e]  ; isotope

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

More importantly than shorthand, it provides a single value notation for isotopic forms:

 >> make object! [x: unmeta [d e], y: the :[d e]]
 == make object! [
     x: :[d e]
     y: ':[d e]
 ]

The whole "molded representations of objects" issue is up in the air, of course. But I think there is broader importance to the idea that you can use single literal values that produce any state of a variable after evaluation.

The ; isotope is a little hard to see. The web console might stylize it more.

We may actually want to keep the colon on it, and say it's a GET-BLOCK! (or whatever) isotope.

>> thing: spread [a b c]
== :[a b c]  ; isotope

Adopting this systemically may make things a bit uglier, such as by ^META operations yielding a decorated block:

>> ^ spread [a b c]
== :[a b c]

But it could give reason to a new theory of what sorts of things have isotopes: the only things that have isotopic forms are things whose evaluator behavior is to produce isotopes of themselves.

So that would explain why there are no plain BLOCK! isotopes, or SET-BLOCK! isotopes, or INTEGER! isotopes... because there's no single value that can be ^META'd to get the non-isotopic form.

And it would explain why ACTION! values evaluate to isotopes of ACTION! (which then, accessed via WORD!, will run the action vs. erroring...in a peculiarity of how action isotopes behave).

Though it twists a little the plan for : becoming UNMETA:

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

Because it would be saying that UNMETA of a BLOCK! is a GET-BLOCK! isotope.

Anyway, like I say... some bendy stuff. It's being invented. Have to look at what works across the various inventory of complex scenarios that have accrued.

1 Like