Could strings have context?

A common problem faced within Rebol is passing string-based templates to functions without any context attached to them. What would it take/cost to add context to string values? An example:

my-context: make object! [
    x: 10
    template: "x"

reduce load my-context/template

The thought would be a string in source would adopt its parent context, otherwise would be unbound.

1 Like

The CSCAPE templating function used in generating C code files during bootstrap is a very pertinent example of this problem, e.g. here:

make-inline-proxy: func [
    return: [text!]
    internal [text!]
    cscape/with {
        inline static $<Returns> $<Name>_inline($<Wrapper-Params>) {
            $<opt-return> $<Internal>($<Proxied-Args>);
    } reduce [api 'internal]

Part of why it works in bootstrap without having to supply more context is that the boot process really uses the user context as the place where most stuff is put. It's somewhat ad-hoc. If it were broken down better with more locals, then CSCAPE wouldn't know where to look.

One thing Ren-C can do that historical Rebol and Red can't is to be able to locate function parameters and locals from the binding of any argument or local (or the literal FRAME! value itself). It's not a solution, but it's better than in R3-Alpha where if you asked for the binding of a function local or arg you just got "true", and there was no ANY-CONTEXT! to find your locals from.

No real solutions off the top of my head, just agreeing that this is something that needs to be thought more about.

In Rebol's current model, the only viable way to do this is to pair up the bindings you want with the string, something like:

template: [(x y z) "$x and $y and $z"]

Definitional binding moves in a wave, and you only get the chance to make that binding at that moment in time...after which the entity describing the binding environment no longer exists.

Some alternative model might allow the string to capture a pointer to an abstract entity which represents the memory of that binding environment--so you could ask it to look up x and y and z based on that pointer.

This is similar in a way to the aspirations of virtual binding. But virtual binding is intended to act as a light surrogate for adding only a few bindings to code (and maybe flattening them out into a copy if the lookups seem to be happening too often to virtualize). It seems on the surface that trying to recreate the entire binding environment of a string in a reified object could be prohibitive.

But who knows, there may be magic along the lines of persistent vector which could cull the total number of binding environments to something manageable. These things are big unknowable research problems in their own right. It's hard to say what can be done if it hasn't been invented!

I think it's important to keep an open mind and consider the idea that much of the current binding mechanics might have to be thrown out. I'd even be willing to consider linking to another engine (Haskell, Clojure, Graphd, etc.) and delegating the task of binding and management to something within their methodology--for prototyping purposes. Then once we see what might be done abstractly, we could think about how a from-scratch low-level C solution might match it.

The trick is to come up with new superpowers without breaking old behaviors that were are important. What we get from today's Rebol is a minimum baseline of expectation of the system's abilities.


There is a slot in strings that I do not think will get used if it is not used for binding. That means the cost for such a feature storage-wise could be low. We can say that BINDING is a common property that all ANY-SERIES! have, and make use of the space.

But it raises many of the same questions that come up with whether a function inside an object should just get "tagged" with the binding of whatever is running MAKE OBJECT!. Does this apply only to "source-level" strings, or if you access through a variable does it count too? e.g. what if it said template: other-string ? If the bindings are useful, then overwriting them arbitrarily would seemingly make them less useful.

This makes me wonder a bit if METHOD could be a more general tool. Maybe it's a better use for MY than its current "me-like" operation? (I'll take MY-CONTEXT off the context name of the example...)

some-context: make object! [
    x: 10
    template: my "x"

So MY would be the tool for grabbing a context out of the left SET-WORD! (or SET-PATH!? that possible?) and then slapping the binding onto the thing being assigned into it.

This would mean METHOD could be done as MY FUNC...though that would only give you the binding, it wouldn't get the implicit <in> for inherited context variables. :-/

Anyway...point being: I think allowing you to BIND strings to contexts is an interesting idea, but it's another case where trying to do such bindings automatically is questionable and could undermine the purpose.

Some alternative model might allow the string to capture a pointer to an abstract entity which represents the memory of that binding environment--so you could ask it to look up x and y and z based on that pointer.

This is similar in a way to the aspirations of virtual binding.

I still think there's some great reckoning in binding that needs to happen. The bad news is that I haven't had any great eurekas about it in a while. The good news is that if I do, the code remains a solid testbed for trying any ideas that can be articulated.

1 Like

The idea is interesting, and I'm with you that binding should have to be explicit.

Now's a good time to be looking at this question.

So with virtual binding, I think we're going to want to programmatically expose the virtual bind chain somehow or another.

>> a-obj: make object! [a: 10]
>> b-obj: make object! [b: 20]
>> block: [a b]

>> viewed: use a-obj (use b-obj block)

>> binding of viewed-with-c  ; new idea: asking BINDING OF on a BLOCK!
== [#[object! [b: 20] #[object! [a: 10]]  ; let's say list of 2 objects

It's technically possible to tie strings into the same basically giving it the knowledge that a block has:

use [x] [
    x: 10
    s: "some-string (x)"  ; *could* pick up `x` awareness automatically

But it doesn't have the natural dampening factor that regular virtual binding has, that whenever you copy something it resolves the binding at that time..dropping the chain. So string bindings would either need to be dropped automatically (and unpredictably) or the virtual chains would just grow indefinitely.

The indefinite growth is especially bad considering it'd be a feature you'd use relatively rarely.

But binding strings explicitly might be okay:

>> obj: make object! [x: 10 y: 20]
>> viewed-str: use obj "The (x) and the (y)"

>> binding of viewed-str
== [#[object! [x: 10 y: 20]]

I think it's misleading to do this kind of operation with BIND if BIND is presumed to be mutating. Because it's not like you'd be giving this binding to all instances of the string...just the result would have the "view". So it's "virtual" must save the result to use it.

e.g. this would be meaningless:

>> bind str obj  ; no result saved would mean it did nothing

This makes me feel like BIND on WORD! is misleading... and maybe we should go with use obj word instead of bind word obj.

Anyway, I might work up a test on this strings-having-context concept here in a bit.


So the feature this ties into in other languages is called "String Interpolation" (Wikipedia).

I've started to feel that supporting string interpolation is fairly important.

This means that a string would have to carry a capture of its binding environment, somehow.

I managed to work up a small test using Sea of Words + Virtual Binding features, which showed some promise in being able to have a function take a TEXT! as a parameter and be able to look up variables in the attached binding of the text.

The capture of that environment might be something explicit done by the function receiving the TEXT! value. But it wouldn't be something the user of the interpolator had to do anything special in order to take advantage of.

As binding is rethought and considered, the important thing is not to get hung up in the historical mechanics. Instead, the question is to ask about the user experience...what can you and can't you do--what works and what does not. One of the positive aspects of having tons of existing code is that if any new feature breaks something that used to work, I find out about it quickly.

Anyway, string interpolation is on the radar as one of the "things we want".