Indexes And Binding Positions

The fact that blocks have a "somewhat hidden" index in them creates no shortage of problems system-wide. The semantics of this index are quite nebulous. COPY doesn't see backwards, for instance:

r3-alpha>> data: [a b c]

r3-alpha>> pos: next data
== [b c]

r3-alpha>> copied-pos: copy pos
== [b c]

r3-alpha>> back pos
== [a b c]

r3-alpha>> back copied-pos
== [b c]

Your new copied-pos only has parity with the original pos so long as no one ever uses BACK on it. This semantic question is inherent to all kinds of functions that accept blocks that aren't at their head. How about the low-level MAKE FUNCTION! ?

r3-alpha>> spec: []
r3-alpha>> body: [print "one" print "two"]

r3-alpha>> test: make function! compose/only [(spec) (next next body)]

r3-alpha>> test
one  ; hey, we skipped past `print "one"` (?!)
two

You'll find quirks related to this everywhere. Ren-C has some implementation changes that make it less likely to just forget that a block encodes a position, but the questions still remain.

How does BIND interact with Indexing?

In R3-Alpha and Rebol2, the index is ignored. Regardless of the input position, the whole block gets the new binding:

rebol2>> x: 304
rebol2>> block: [x x]
rebol2>> reduce block
== [304 304]

rebol2>> obj: make object! [x: 1020]
rebol2>> bind next block obj
rebol2>> reduce block
== [1020 1020]

Red and Ren-C currently give back [304 1020] for the BIND case, so only the positions after are bound.

That's fine and good for BIND. But Ren-C's virtual binding gives you a new view on a block. What happens if you step backwards in that view?

>> x: 304
>> block: [x x]
>> obj: make object! [x: 1020]
>> virtualized: in obj next block
>> reduce head virtualized
== [1020 1020]

There's nothing in the virtual binding information that says it only applies after a certain index in the block. So if you take that step backward, you get the bindings.

It's technically possible to put an index position into a virtual binding...but you'd also have to change the binding to remove the index after stepping down into a nested block, because the index as limit only applies at the top level. So if instead of [x x] the above block had been [[x x] [x x]], the you'd want it to act as [[304 304] [1020 1020]] with the splitting at the top level, not keep splitting all the way down as [[304 304] [304 1020]], thinking the "start at the second item" applied to the nesting too.

But even if we did that, there'd be a problem with mutations. Although virtually bound views of blocks are CONST, that's only a suggestion...you're supposed to be able to switch it to MUTABLE if you're sure you know what you're doing. And the original block being input isn't even made CONST.

Does LET Make This Worse, or Just The Same?

At first I thought LET was worse, because it's an active instruction in a block to change the binding. Meaning if you reposition in it and run it again, you get the binding twice.

But I think the main issue with that is kind of tangential. You probably shouldn't be capturing a BLOCK! with accrued LET state in the first place; I talk about that in Should the Evaluator Have State Besides A Block?. It seems that's a job for FRAME!. So really, the places that capture virtual binding state are whole blocks, where it's their stored index position that creates the problem just like any other IN OBJ at a non-head position would.

Best Solution...Shallow COPY If You Care?

As semantically crappy as the index positioning in Rebol is...historical usage points out that quite a lot of code doesn't really wind up caring about this problem. Most block operations are on heads of arrays...and most evaluative and binding operations never step backwards.

Offhand I feel like it would be a losing battle to try to encode a position into virtual binding. It's probably more efficient to just have those places that have a reason for fine-grained control to make a shallow copy to prevent stepping backwards altogether.

There really are just going to be limits in a system that embraces mutability as much as Rebol historically has...the ergonomics and semantics are going to be perpetually at odds. :-/

2 Likes

I think people program around this feature, taking profit when only part of the info is needed in the copy, or using copy head in the other case.

As usual I my needs are limited so the function example had not crossed my path.