All-SPLICE!: REDUCE, DELIMIT (SPACED, MOLD...)

COMPOSE was a sort of poster child for the use of SPLICE!, so that you could mix and match splice cases with non-splicing ones... carried by the values, with no concern for /ONLY:

>> block: [a b c]

>> compose [(block) d e (spread block)]
== [[a b c] d e a b c]

But when suggesting the more-obviously-a-verb word SPREAD for the operation, @rgchris gave this example for REDUCE behavior, which hadn't occurred to me previously:

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

I'm guessing most people would be in favor of having the splicing behavior. Arguments that say that there should be a 1:1 correspondence between expressions and values in a REDUCE are already pretty much out the window, since VOID elements vanish (including conditionals that don't take any branch).

So I implemented this! (Circa October 2022)

SPLICE! Handling Is In DELIMIT & Friends Now, Too...

I've complained in the past that the often random-seeming treatments of blocks in Rebol2 functions like REJOIN lead to problems--and that it would be better if people had to be explicit about their intent. This offers the ability to "inherit" whatever the enclosing delimiting strategy is, and fold into the existing operation (technically more efficient):

>> block: ["c" "d"]

>> spaced ["a" "b" block]
** Error: BLOCK! not handled by DELIMIT, use SPREAD or desired string conversion

>> spaced ["a" "b" spread block]
== "a b c d"

>> spaced ["a" "b" unspaced block]  ; if you wanted another interpretation
== "a b cd"

I believe I prefer this over having some default way that blocks behave inside string conversions. The odds of guessing right are low enough that it's better to have people be explicit.

4 Likes

3 posts were split to a new topic: Does SPREAD of a BLOCK! Affect Other References?

And What About MOLD, You Ask?

Historically, the way you would ask to mold an array without its delimiters was with MOLD/ONLY:

rebol2>> mold [a b c]
== "[a b c]"

rebol2>> mold/only [a b c]
== "a b c" 

In Ren-C this raises some existential questions, like how should quoted arrays be handled?

mold/only first [''(a b c) d e]  ; ???

But I think there's a better answer: use SPREAD and SPLICE! isotopes. If you want to mold the contents of an array, then turning it into a splice seems the natural answer. And since you can't have splices of quoted things...there's a nice unambiguous answer.

>> mold [a b c]
== "[a b c]"

>> mold spread [a b c]
== "a b c"

It does raise the question of what to do if you have something that might be an array or might not. How do you tell it to mold as is if it's not an array, or without the delimiters if it is? That's what MOLD/ONLY did, after all:

rebol2>> mold/only [b l o c k]
== "b l o c k"

rebol2>> mold/only <tag>
== "<tag>"

"SPREAD won't SPREAD tags..." you say. And no, it won't. But I think this is a rare case... and the neat thing about putting the bit on the value (as opposed to a refinement) is you can make functions like SPREAD-OR-AS-IS. Or SPREAD-IF-PATH-OR-GROUP. You can really tweak this however you want.

>> mold spread-or-as-is <tag>
== "<tag>"

>> mold spread-or-as-is "[b l o c k]"
== "b l o c k"

>> spread-if-path-or-group 'p/a/t/h
== \~[p a t h]~\  ; antiform (splice!)

>> mold spread-if-path-or-group 'p/a/t/h
== "p a t h"

How about THAT? All of this hinges on the idea that MOLD doesn't generally know how to mold isotopes, as they have no representation. It just chooses to interpret the request to mold a splice isotope as "contents matter, no delimiters".

More control, more clarity, and the death of another /ONLY. What more could you ask for?

1 Like

On The Theme of ALL-SPLICE!...

Historically I discouraged the idea of "too many functions taking splices".

One good reason would be that SPLICE! carries no binding.

Hence if you were going to do this:

>> reduce ~[1000 + 20 300 + 4]~
== \~[1020 304]~\  ; antiform (splice!)

Then REDUCE would have to assume you wanted the binding from the callsite (the way COMPOSE does by default).

Today's REDUCE uses the binding of the thing you pass it... and I think NOT using the binding of the callsite should be the overwhelming default for functions.

But... COMPOSE is different. It can operate on unbound material (strings, even!) (or: "strings, especially!!!")

So is there any good reason to prohibit it for COMPOSE?

:thinking:

I was looking at this piece of the implementation of the SOURCE function:

    keep opt spread compose [
        return: (conditional ret.spec)
    ]

    for-each [key param] f [
        keep spread compose [
            (decorate param key) (opt param.spec)
                (opt param.text)
        ]
    ]

And I thought "that gets tighter if I could use a SPLICE!"

    keep opt compose ~[
        return: (conditional ret.spec)
    ]~

    for-each [key param] f [
        keep compose ~[
            (decorate param key) (opt param.spec)
                (opt param.text)
        ]~
    ]

Is that so wrong?

I think it makes things more obvious... you can look at it from a few feet away and go "oh yeah, a splice!"

It's also more performant. You duck a function call to SPREAD.

Will People Start Expecting This?

I get a bit worried about people failing to grasp the nuance of why COMPOSE can do it, and REDUCE can't.

And it seems like it could open the floodgates to questions like "should JOIN take splices?"

>> join ~[a b c]~ [1000 + 20 300 + 4]
== \~[a b c 1020 304]~\  ; antiform (splice!)

How about FIND? Does SPLICE! have a position... is it a series?

As useful as these things may seem, I have a sneaking suspicion that they're a bad idea. You start to lose grounding pretty quickly. (I've explained why I don't think QUOTED! or QUASIFORM! etc. type things should be supported by generic operations.)

I Think It's Okay For COMPOSE

COMPOSE may be an odd one out, because it's a templating thing. Something about its nature may just be more suited to "I work in whatever skeleton you gave me."

Certainly I'd use it.

1 Like