Case Study: Synonym Mapping in Redbol

In the rgchris-scripts Ren-C folder, there was a file doing some mapping of terms... in a Redbol kind of way.

I'll trim it down to just this:

for-each [word value] [
    function! action!
    string! text!
    foreach for-each
    ; ...
][
    if not in system/contexts/lib word [
        extend system/contexts/lib word get value
    ]
]

Today You'd use EXPORT With Modules

You can export on definition at a time, and it's "relatively efficient" to do so. (e.g. you aren't paying dramatically more due to expanding an object on each export, vs gathering all the things you're going to export up at once at module load time).

And it can look pretty nice, like this:

for-each [proxy word] [
    function! action!
    string! text!
    foreach for-each
    ; ...
][
    export (proxy): get word
]

But beneath the nice looking exterior lies a lot of devilish details, which have to be considered

Good News: Assigning-ACTIONs Protection Works!

The pattern you see here of var: get word is the perfect example of what "action packs" are designed to protect you from. The goal is protection by default, and here you get it!

:clap:

Because GET is not a dedicated function generator, it doesn't return an unstable "action pack" antiform... just a plain ACTION! antiform. And that won't do normal assignment.

So this case (properly!) needs to take another tactic. In this case, that would be a meta-assignment... because you're saying the right hand side might be either an action or not.

export (meta proxy): get word  ; e.g. (export ^foreach: get $for-each)

...or, as it's looking like the carets will distribute into the groups...

export ^(proxy): get word

As I've said, this is exactly what I was looking for: to shift the burden of decoration onto the person writing the weird assignment. And this certainly counts as a "weird assignment"... these are the sorts of cases it makes sense to make.

Inconvenient Truth: What If proxy Is Bound?

We wrote export (proxy): get word e.g. act as export string!: get $text!. We know you can't GET that ACTION! word unless it has been bound. But what if the STRING! word is also bound? :thinking:

Since it's EXPORT's job to export definitions, you might think it would be "clear" that if there was some binding on the proxy word like STRING! word it's "meaningless"... and it's meant to be just used as a label for a new export from this compatibility module. But that ignores the fact that EXPORT is itself--built on top of BIND and SET.

This rears its head quite frequently: how can we be sure when it's a good idea to ignore bindings?

I'm leaning toward reversing today's "you have to ask for binding" bias. Instead, automatic descent binding may become a thing you have to suppress with quote-marks in dialects. If EXPORT were going to complain if you tried to export something with a binding, you 'd have to say:

for-each ['proxy word] [  ; <-- tick means 'PROXY not bound, WORD bound
    function! action!
    string! text!
    foreach for-each
    ; ...
][
    export (proxy): get word
]

Saying that you have to be careful about not getting stray bindings on your WORD!s is probably a good conservative default for now -- because you're always going to have cases where a stray binding would override a binding that you meant.

It could be that EXPORT is one of the cases where it's sufficiently "obvious" that you should just strip off a binding. But my instincts on this are that the more places you get reminded of what's being bound and why... it's probably for the better.

I should mention that pursuant to concept of GET, there is another idea here... which is perhaps supporting if the word itself carries decorators saying it's a known action, to GET it as an action pack:

for-each [proxy word] [
    function! action!
    string! text!
    foreach for-each/   ; or `/for-each`
    ; ...
][
    export (proxy): get word
]

That would work just like:

export foreach: for-each/

The reason that works is that what FOR-EACH/ does is ensure that FOR-EACH is an ACTION! and then puts it into an action-pack, hence allowing you to assign it on the left undecorated. So GET would just mirror that behavior when you passed it something with a slash on it... "as if it were the evaluator".

This does push the questions about the "safety curve" around. It isn't about some universal safety of the GET function, it's about the properties of GET across a certain set of inputs.

If you were thinking about "safety" from the sense of software security, then it might seem action packs didn't move the needle. But the point wasn't really "security" rather just preventing accidents. And the idea here is that the more decorations you use the more specific you are being; so what's really being made safer is what you're doing almost all the time...being able to get your work done with the currency of plain old WORD!s...

Even still: if you wanted a "safer GET" you could build one. Restrict the input types on your default GET, and then fall back on LIB/GET (alias it as GET* or somesuch) when you really need it.