Where DO Variables go when you DO From a Module?

Classically, the idea of DO of a string is that it adds the variable to the ever-growing user context.

>> do "x: 10"
== 10

>> system/contexts/user/x
== 10

>> x
== 10

It's because it lives in the user context that you can see it just by typing X in the console, as the console binds everything to the user context as well. (So the console is really just running do {do "x: 10"} there.)

But modules have their own contexts they are bound to. They inherit LIB, but are not connected to USER. So you wind up with a problem... (not Sea-of-Words-specific, this existed in R3-Alpha as well):

r3-alpha>> m: module [] [do "x: 20", print [x]]
** Script Error: x word is not bound to a context

r3-alpha>> x
== 20  ; outside the module, visible to the user context.

It Doesn't Matter if It's a String or a FILE!/URL! that Points At One

When you say do %some-file.r then if it's not a module, that's no different than a string. There's no extra information.

Why Can't It Just Use The "Currently Running Module"...?

...there...is...no...currently...running...module...!!

Consider a case where you make and export your own custom IF-like construct from a module. Then someone will use it and pass it a block... like custom-if condition [do "x: 10"]. So when the DO runs the stack is actually underneath the CUSTOM-IF. You wouldn't want it declaring X in the custom if module.

So the question goes along with a lot of other unfortunate things, like not being able to get the "currently running function" in the way that people might often think they can get it. The language simply doesn't work like that. And not working like that is intrinsic to its idea of not drawing a line between keywords and functions.

Could DO be defined per-Module, as RETURN is per-Function?

...maybe. :-/

But this would mean that any function that made use of DO in its implementation could wind up using the wrong DO. A utility library that ran DO would be using the version from the utility library.

 module [Name: Utility-Library] [
     do-helper: func [source] [
         print "Adding helpful behavior to DO!"
         do source
     ]
 ]  

Anyone who called DO-HELPER with %some-file.reb or "x: 10" would wind up creating any of the definitions inside of the Utility Library.

That seems...wrong.

What if instead of DO, there was a per-Module INTERN function?

Another idea builds on something we previously discussed, which was that strings themselves might have binding.

So what if a TEXT! could have a binding saying what module it belonged to, and you could use a function provided on a per-module basis to glue that module's binding onto it?

 module [] [  ; has its own BIND-STRING-TO-ME function defined
     do bind-string-to-me "x: 10"  ; attaches module to string, DO heeds it
 ]

This would be able to get past the DO-HELPER scenario.

However, you'd also have to do bind-string-to-me %some-file.reb as well. Meaning DO would be heeding the binding of a string only tangentially related.

I've proposed some wilder ideas about DO over time, like that do 2 could mean "Run the code in notepad 2". The further you get away from the string to process and getting into "values that is used to figure out to find the string to process", you find things that not only don't make sense to be carrying bindings but that may not be able to.

String binding is a somewhat interesting feature, but probably this is a bad idea.

So, Should Everything Just Act Like Modules?

One way of dispensing with the question would be to say that every string you execute effectively lives in its own module.

If that's not what you want, you need to transcode the source and bind it manually.

So this would mean isolation becomes implicit on all scripts. The difference between DO is if you are calling something that is intended to have side-effects and want to pass it args (maybe) and get a result...while IMPORT is cached (only one instance of a module is used for all IMPORT, and you are asking to get access to some set of definitions inside of it).

The compromise could be that each module gets an INTERN function specific to it, which means "make this string or code part of this module".

This probably makes the most sense. Will pursue this direction.

1 Like

I notice this was talked about in R3-Alpha.

BrianH's summary:

While non-module scripts can ignore modules and use DO or the IMPORT function, modules need to be written modularly.

This is by design, not a bug.

Reading between the lines, he's saying there are two different models of programming...the "Script" universe and the "Module" universe. But the script universe will basically set you up for a methodology that won't work at all in the module universe.

Abstractly speaking, is the following good?

>> x: 10
== 10

>> do "x: 20, reduce [<result> x * 10]"
== [<result> 200]

>> x
== 20

...or does it seem like that should require some additional step, to make the connection between the X in the string and the X in the user context?

I think we'd be better with a default like:

>> x: 10
== 10

>> do "x: 20, reduce [<result> x * 10]"
== [<result> 200]

>> x
== 10  ; the string's X was isolated

But what I'm proposing is that every module context gets its own custom INTERN function...which combines LOADing with binding on strings, and can be used on blocks too.

>> x: 10
== 10

>> do intern "x: 20, reduce [<result> x * 10]"
== [<result> 200]

>> x
== 20

INTERN would be a specialization of a lower-level INTERN* function that took where to do the interning as a parameter.

I'll be referring to this concept as "isolated DO".

2 Likes

Unless it was meant to use the "global" x with the DO of the string.

It is hard to predict what someone is trying to DO (pun intended).