Splicing Semantics: APPEND with PART To Strings

You likely aren't surprised by this:

r3-alpha/red>> append/part [abc def] [ghi jkl mno pqr] 2
== [abc def ghi jkl]

You asked for 2 units of the appended value's /PART, so you just got [ghi jkl] out of the spliced (by default) block.

But is this what you expected?

r3-alpha/red>> append/part "abcdef" [ghi jkl mno pqr] 2
== "abcdefgh"

To me that runs counter to the idea of what /PART is supposed to mean. (Not to mention, it doesn't seem all that useful.)

We've fancifully talked about /PART being something that would be encoded as a property of lists, e.g.

>> part [ghi jkl mno pqr] 2
== **[ghi jkl]**  ; special lightweight "view" on list

If this were the case, then we might rewrite append/part "abcdef" [ghi jkl mno pqr] 2 as:

append "abcdef" part [ghi jkl mno pqr] 2

In which case we would expect that to act like:

append "abcdef" [ghi jkl]

Not append "abcdef" [gh].

What Red and R3-Alpha are really doing are more like:

append "abcdef" (part stringify [ghi jkl mno pqr] 2)

Where STRINGIFY in this case isn't any well-known operation. It's not FORM (because there's no spaces), it's not REJOIN or AJOIN (it doesn't reduce). The only real definition of it would be "what you get when you append a block to a copy of an empty string." :roll_eyes:

Ren-C Now Uses A Consistent Interpretation

>> append:part [abc def] spread [ghi jkl mno pqr] 2
== [abc def ghi jkl]

>> append:part "abcdef" spread [ghi jkl mno pqr] 2
== "abcdefghijkl"

>> append:part #{ABCD} spread [#{12} #{3456} #{78} {9000}] 2
== #{ABCD123456}

It always speaks about the length in terms of the value you are appending. If the value isn't a splice or series, I think it should probably be an error (though I haven't enforced this yet...)

>> append:part "abcdef" 1020 2
** PANIC: PART can only be used with series or splices
1 Like

I knew I'd seen this discussed before:

https://github.com/metaeducation/rebol-issues/issues/2096

https://github.com/red/red/issues/599#issuecomment-30111489

Interestingly at the time I seem to have found the argument DocKimbel made to be "fair enough":

  1. existing behavior was well-defined
  2. it was calculating something you could not calculate on your own easily
  3. you could work around it by using COPY/PART

But over time I see (2) as more of a flaw than a strength; it's more a matter of having flawed behavior and leaking random noise than it is "calculating" anything.

I may have to think a bit more about this, but at least I'm pinning down the places in the source where these decisions are made--cleaning up some messy code.

Speaking of cleaning things up... while looking at this I noticed something else:

red/rebol2>> append (skip "abcdef" 3) "ghi"
== "abcdefghi"

I didn't realize APPEND was returning the HEAD OF the series. I thought it was supposed to be returning the position you passed in.

I've changed it to that:

>> append (skip "abcdef" 3) "ghi"
== "defghi"

This example demonstrates why the behavior is desirable: it's strictly more useful. If you wanted the HEAD OF you could get it:

>> head of append (skip "abcdef" 3) "ghi"
== "abcdefghi"

But if APPEND throws away its knowledge of the original position, then you can't get it back--as when it was calculated by an expression here.

I'm assuming the reason that it did HEAD OF was because APPEND was born of an idiom of usage of INSERT, and it was the "cheap" behavior that didn't require storing the input series in an intermediate variable. But with APPEND as its own entry point, that reason goes away.

2 Likes