Reacting to TIE ($) To Automate Binding

The biggest realization of "modern Binding" in Ren-C was that working in a mostly unbound world needed to be the default.

What had held up early attempts to "virtualize" binding was that I was trying to automatically spread binding information in mechanical operations. e.g. when you would PICK a word out of a BLOCK!, the picked word would come back suitable to perform a GET on. It did this by projecting the virtual binding from the block onto the returned Cell.

I gave this as an example of the kind of legacy pattern I was trying to keep working:

double-assigner: func [block] [
    for-each [sw i] block [
        assert [(set-word? sw) (integer? i)]
        set sw 2 * i  ; historically assumes X: and Y: are bound
    ]
]

double-assigner [x: 10 y: 20]

But this makes an assumption that may not be correct...and in fact, often is not. In a world where unbound material is an effective currency, you can't guess that the user wanted the binding to be propagated. They may have just been interested in the raw material.

So this spawned the dawn of "conscious binding", where you had to use specific operations at each step:

double-assigner: func [block] [
    for-each [sw i] block [
        assert [(set-word? sw) (integer? i)]  ; both SW and I are *unbound*
        set (inside block sw) 2 * i  ; <-- INSIDE BLOCK makes all the difference
   ]
]

double-assigner [x: 10 y: 20]

We can debate the BIND "dialect", and whether the INSIDE operator is the right one or not... but this captures the general spirit of things.

Using Our Dialect Powers...What About the TIE ($)?

So in this FOR-EACH example, we can already imagine a convenience... of being able to annotate variables to say you want to propagate the binding (or "tie" the variables to the binding of the block). How about this?

double-assigner: func [block] [
    for-each [$sw i] block [
        assert [(set-word? sw) (integer? i)]  ; SW bound into block's context
        set sw 2 * i
   ]
]

double-assigner [x: 10 y: 20]

That's convenient!

What If You Wanted A ^LIFT-ed Bound Variable?

There's also @PIN-ned variables which means "reuse an existing variable, don't make a new one".

This means we need the "Sigil-Composition" that @bradrn has spoken up for. But instead of accomplishing it with BLOCK!s the way I've been doing, we now have some lighter options...

 for-each [@:$sw i] block [...]  ; reuse and bind

 for-each [$:^sw i] block [...]  ; bind and lift

 for-each [^:$sw i] block [...]  ; lift and bind (synonym, we assume?)

 for-each [^:$:@sw i] block [...]  ; lift and bind and reuse

It's not the prettiest thing in the world, but I don't imagine you'd usually need more than one Sigil.

Maybe you could put them in BLOCK!s to help visually:

 for-each [[^:$:@sw] i] block [...]  ; lift and bind and reuse

Did that help? It kind of did. CHAIN! seems to make the most sense... visually, and also because it doesn't really have any semantic baggage outside of trailing and leading colons. And really only trailing colons have an obvious meaning.

Would it be better if the colons weren't there?

 for-each [[^ $ @sw] i] block [...]  ; lift and bind and reuse

I dunno. I almost want to cluster the sigils together separate from the word:

 for-each [[^:$:@ sw] i] block [...]  ; lift and bind and reuse

We can debate this, but somewhere in here there will be a solution.

What About Sigil Meanings On Lists Themselves?

One of the earliest thoughts I had regarding the applications of @ as a "inert" Sigil was "what if REDUCE took this as a signal that the data was already reduced.

>> reduce [1 + 2 10 + 20]
== [20 30]

>> reduce @[1 + 2 10 + 20]
== [1 + 2 10 + 20]

That might not seem useful, but it sort of is. For instance, PACK uses this:

>> pack [1 + 2 null]
== ~['3 ~null~]~  ; anti

>> pack @[1 + 2 null]
== ~['1 '+ '2 'null]~  ; anti

This comes up, when you've got a branching structure and you have some branches that want to return evaluative products and others that you want to return a pack of literals.

So the convention spread other places, e.g. ANY and ALL where you can run predicates on data and either reduce it or not.

Could this same idea of pushing the convention onto the lists be useful... e.g. to make BLOCK!s that you "auto-bind" out of when you pick?

>> x: 10

>> tieblock: @ $[x y]
== $[x y]  ; bound by the lone PIN operator (a.k.a THE)

>> tieblock: the $[x y]
== $[x y]  ; bound - if you prefer words vs. symbols

>> tieblock: $ '$[x y]
== $[x y]  ; bound - if you want another way to say the same thing

>> tieblock: bind here '$[x y]
== $[x y]  ; bound - just for the sake of completeness...

Okay, you get the idea on the binding. But my question is: would there be operators that upon seeing a TIED! list, would automatically bind when you picked things out of them?

So instead of the binding behavior coming from the variable you were using in a FOR-EACH, it would come from the data. Or maybe even just a PICK would work:

>> pick tieblock 1
== x  ; bound

>> get pick tieblock 1
== 10

So if it was an ordinary block..

>> x: 10

>> block: [x y]

>> get pick block 1
** Error: X not bound...

>> get pick tie block 1
== 10

This definitely turns Sigil-carrying entities into beasts-with-behaviors. But since the common currency in the system is normal BLOCK!/GROUP!/FENCE!, it may be that most of the creations of these beasts would be transient... existing only long enough to cue the PICK or FOR-EACH or whatever to do its behavior.

You Don't Know Until You Try... :man_shrugging:

I'm worried about there not being some mechanical terra-firma. PICK seems foundational. So perhaps there's some difference here, e.g. between PICK and another operator like SELECT could heed the sigils on the target data (?) And FOR-EACH seems foundational too, but I think decorating the variable is fair game.

Anyway, it's an area I'll start experimenting with and report on whether any of the experiments turn out to make things clearer/better or worse. (!)

So that's a pretty terrible idea, and I knew it was crappy when I wrote it down.

To recap: the fundamental operations like PICK operate in the "mostly unbound world"... and mechanically pick things without having them bridge the binding from the container onto the thing you picked. I was looking for some other place to put the cue--so your PICK product could have a binding, without needing to do further operations on it.

Treating $[...] and [...] differently as the thing you are PICK-ing out of is bad, they're conceptually just lists and you shouldn't worry that your code is going to act differently on basic operations like PICK on them.

But the good thing about writing down terrible ideas is helping you see where the modification needs to be made to make it non-terrible.

When that post was written, $1 was a MONEY! and there wasn't a generalized Sigil system. BUT NOW... that idea of MONEY! is dead, and we can put [$ ^ @] Sigils on anything and everything... e.g. $1 is a "TIED! INTEGER!"

How about THIS:

>> x: 10

>> block: [x y]

>> get block.1
** Error: X not bound...

>> get block.$1
== 10

>> get block.$(2 - 1)
== 10

:bullseye:

cc: @bradrn

1 Like

Note what wouldn't be the same here:

>> get block.($1)
** Error: can't PICK $1 in block

We don't know what $1 does. Most likely errors (bound INTEGER! are not likely to be a good idea, but, who knows). Even if bound integers were decided to be okay, $1 would produce 1.

This also would not be the same:

>> get block.('$1)
** Error: can't PICK $1 in block

That's trying to use $1 directly as the "picker" and is a completely different thing. That would be for like if $1 was a key in a MAP!. It has nothing to do with binding.

When you aren't using a GROUP! like that, and things are just appearing literally in the tuple, they are dialected.

That means this also would not work:

>> get pick block '$1
== 10

There'd have to be a separate out-of-band parameter to PICK to do this, like a :BIND refinement or something.

>> get pick:bind block 1
== 10

CHAIN!-based compound SIGIL! won't work here

Let's say you're indirecting through a variable:

>> n: 1

>> block.(n)
== x

That has a shorthand (not just shorter in terms of one character, but more efficient as it can bypass the evaluator, and doesn't use a list type which makes a separate allocation):

>> block.@n
== x

If you want to ask for binding, then this is no good:

>> get block.$:@n
== 10

That's seen as a 2-element CHAIN! of [block.$ @n], not a 2-element TUPLE! of [block $:@n], due to the hierarchy of sequences.

"And nothing of value was lost", that looks much better as:

>> get block.$(n)
== 10

I've proposed that the sequence mechanics might optimize this pattern so that (n) in a sequence doesn't actually cost more than @n, and this is another motivator for that optimization.

1 Like

A post was split to a new topic: Literal Arguments As Proxies For Dialects