Consequences of if the TO Operator Can't See Binding

R3-Alpha (and Red) see value cells as a single data type. It doesn't matter what kind of code you have inside the interpreter, it always has the same level of privilege.

That's mostly true of the C build of Ren-C, too...with the exception of the C const attribute. It compiles everything down to just one type, the Value(*).

But the C++ build offers more granular views:

  • A Cell(*) cannot have its binding looked up unless you have a "specifier" to couple it with.

  • A noquote(Cell(const*)) is a view of a cell without its quoting level -or- its binding.

What's neat is that since these are really just views of the same memory, all the C++ checks are doing is stopping you from calling methods you shouldn't on each type. This doesn't cost anything extra at runtime (in the release build).

Methods Can Receive Different Views

As an example, the MOLD mechanics for each type only receive a noquote(Cell(const*)). The higher-level of Mold_Value() takes care of outputting the right number of tick marks, and then the value is supposed to output based just on the cell contents.

Besides not being able to react to the quoting, it means a MOLD can't look up a WORD! it is being asked to mold, and decide how to render it based on what it looks up to. Is that a good rule? I'm pretty sure that it is. While someone might want a way of generating string output from source that does take bindings into account, that would likely use MOLD in its implementation. But I believe it's safer (and faster) to say that MOLD is constrained in its definition to operate just on the cell structure without being able to look up bindings...or render something differently based on how many quote marks it has in front of it.

Could the View Given to TO Help Define It?

When examining qualitative differences between MAKE and TO (and AS and CAST and ALIAS and anything else) seemed to me that it was best if TO remained very "surface-level".

We could phrase this in terms of general rules like "a TO can't THROW". So with that, we might say that it seems sensible that a TO doesn't REDUCE its input. Further stepping in the definitional sphere, we could say that TO can't dereference or GET a WORD! in its input at all.

(Note: This isn't to be confused with saying that a usermode methodization of TO couldn't REDUCE code or use GET in its implementation. It just couldn't react to anything about the binding of the input that it was receiving.)

Not Reducing During a TO Seems Uncontroversial

Rebol2 wouldn't let you do TO an OBJECT!:

>> to object! [a: 1 + 2 b: 3 + 4]
*** Script Error: to does not allow object for its type argument

(NOTE: Not that I think it's necessarily a great idea to make TO do this...but I will point out that CONSTRUCT was an idea of being able to make objects without you might imagine TO of a BLOCK! doing that behavior under this model.)

R3-Alpha and Red's "TO MAP! didn't reduce the contents:

r3-alpha/red>> to map! [a: 1 + 2 b: 3 + 4]
== #(
    a: 1
    +: 4
    b: 3

So saying that TO shouldn't have access to the bindings of variables seems like a solid rule.

The TO BLOCK! and TO GROUP! Idiom Would Need To Change

While the TO itself may not use bindings, conversions between arrays have historically preserved them.

So... what if TO stripped off bindings? Would that be bad?

It doesn't seem too inconsistent to say that the results of a TO are not bound. For instance, look at the historical behavior of TO BLOCK! of a string:

rebol2>> x: 10

rebol2>> b: to block! "x + 20"
== [x + 20]

>> do b
** Script Error: x word has no context

We can also notice that an aliasing operator (like let's say AS) has the benefit of not making a copy if you don't want it. So not only can you keep the bindings by saying copy as group! block, but your awareness is drawn to as group! block to be used if you didn't need that copy.

Mechanical Problem: TO is not DEEP

Historically you do not get a deep copy from TO.

rebol2>> block: [a [b c]]

rebol2>> group: to paren! block  ; ugh, remember PAREN!?  ugly name!
== (a [b c])

rebol2>> append second block 'd
rebol2>> block
== [a [b c d]]

rebol2>> group
== (a [b c d])  ; copy was shallow, change to original block reflected

But if you're shallow copying the contents, that means you're reusing arrays from the original. How do you reuse their memory without also reusing their binding?

Well... we have one possible tool, with virtual unbinding. We throw in a virtual binding patch that says "stop all binding here" and the only bindings that apply are those that are layered on top of the unbinding patch. What this would mean is that even though the TO methods only have noquote(Cell(const*)), we'd offer a combinator for getting a Value(*) back even though you don't have a would be by letting you manufacture a virtual binding specifier that worked to resolve any Cell(*) as unbound. All nice and tidy, and solved precisely with the type system. Neat, eh?

But right now, virtual binds are const. This would mean that if you used TO BLOCK! to get a block and inherited its nested arrays, trying to run a mutable BIND/DEEP would fail on those nested arrays...because it would want to protect you from mutating the embedded blocks with aliased binding. You wouldn't necessarily get the effects you wanted (you'd be changing the view as seen by the original block...the virtual one would stay unbound).

This would mean you could only use a shallow mutable BIND on the result of such a TO, or more virtual binding.

Would Anyone Use This TO of BLOCK! or GROUP! ?

A lot of places that have been using TO really wanted to use AS (or whatever the aliasing operator would be that says "use the same array memory, just view it as a different type).

How often do you want a copy that also happens to be shallow? On those few occasions that you do, isn't copy as group! ... more explicit, along with being easily changed to copy/deep as group! ..., which you can't do with TO?

The real value here is just driving a stake in the ground of what a TO operator is...which has been a very slippery question. If we can agree that whatever it is that it can't see or act on the bindings of what you give it--and enforce that through compiler checks and rules--then we've gotten a little tiny step forward in being able to say what is and what is not...