REBOL 3.0

Comments on: Modules: Free Variables (Globals) are Implied Locals

Carl Sassenrath, CTO
REBOL Technologies
6-Oct-2006 20:29 GMT

Article #0048
Main page || Index || Prior Article [0047] || Next Article [0049] || 19 Comments || Send feedback

The prior article talked about how making modules was trivial and provided an example.

Some of you REBOL experts may be wondering what happens in this case:

REBOL [
    title: "Stock Handler"
    module: 'mod-stocks
]

stocks: []

buy: func [symbol price] [
    last-stock: symbol
    repend stocks [symbol price]
]

sell: func [symbol price] [
    if not symbol [symbol: last-stock]
    remove-all [asymbol aprice] stocks [
        symbol = asymbol
    ]
]

Notice the last-stock variable floating around in there. Although this is not a good programming practice, it is allowed. In V2, last-stock would automatically become a global variable. But what happens in V3 modules?

In V3, such variables are best referred to as free variables of the module. You can think of them as "local globals" for the module. The last-stock variable above would be accessible within the mod-stock module, but not outside it.

In addition, if last-stock was exported from any default modules that are imported by mod-stocks, then it would refer to those exported values. After all, that is the same reason why func, if, not, and all other standard REBOL values are available here. They are imported from other modules.

A bit deeper...

To understand a little deeper why this behavior occurs, you need to know more about how bind works for modules. Keep in mind that modules are independent contexts. They are independent namespaces.

In REBOL V3, when you load a module in this fashion, all of its words are collected into a new and separate namespace. The words are no longer bound to the global context of your main program. They are independent.

But it is very important to note that this only happens when you load a module directly as a file. If you are creating it from other code, then you must heed the warning below.

Beware of prior bindings...

REBOL V3 allows you to create modules not just by loading source files, but also dynamically. The above example module can be created dynamically with code such as:

mod: make module! [...]

Note that when you do this, the words of the module's block are already bound to the context of your current program.

So, if you dynamically create a module, some of its words are already be bound to your current context. Here, last-stock is an example of such a binding:

last-stock: none

mod: make module! [
    [
        title: "Stock Handler"
        module: 'mod-stocks
    ]

    stocks: []

    buy: func [symbol price] [
        last-stock: symbol
        repend stocks [symbol price]
    ]

    ...
]

This is the result of the fact that the module block was bound when bind was invoked for the main part of the program.

You may be asking yourself, "why would I ever want to do this?" Well, what if you want to define in your main program some of the functions (or overwrite some of the standard system functions) that are being used in the dynamically created module?

So, that is an interesting result, and we will want to revisit this later, because it has deeper issues. But, I don't want to go too deep in this acticle.

Note that if you really want the full "unbound" affect, you could unbind the module block with a line such as:

mod: make module! unbind [...]

It works as you would expect, but note that all of the module's free variables are still present in your current context, regardless of the fact that they are not being used.

19 Comments

Comments:

Ladislav
7-Oct-2006 5:10:22
I must say that I am lost here. How (in the "Stock Handler" example) the interpreter finds out it has to export 'STOCKS, 'BUY and 'SELL but not export 'LAST-STOCK?
Ladislav
7-Oct-2006 5:24:16
I am afraid, that the make module! example is not how anybody would expect the things to work. The

#[module! [...]]

way would work "smoothly" with the exception, that it is still a "static" approach and I don't have a good idea how to make it work dynamically.

Volker
7-Oct-2006 5:54:50
looks like a typo. From the last blog: "Note that by default, this module exports all of its defined values. The buy and sell functions are exported, and so is the stocks block." "header. Here is the revised script that only exports the buy and sell functions:"

REBOL [ title: "Stock Handler" module: 'mod-stocks export: [buy sell] ]

Ladislav
7-Oct-2006 5:55:42
The only dynamic way that came to my mind is to expand modules (using MAKE e.g.) as follows:

; this statically creates the new module
new-module: #[module! []]
; this expands the new module
make new-module #[module! [...]]
; etc.

Ladislav
7-Oct-2006 6:07:10
Another option may be to have special construct like

#[unbound a:]

which is the same as

a:

except for the fact, that the word is unbound

Volker
7-Oct-2006 6:10:25
Hmm, that "export:" is easy to forget. Delete a line accidentally.. Maybe enforce an "export: all"? A beginner would copy magic from a tutorial anyway.
Ladislav
7-Oct-2006 6:15:57
using the last, we could write:

mod: make module! [ [ title: "Stock Handler" module: 'mod-stocks ]

#[unbound stocks:] [] #[unbound buy:] func [symbol price] [ #[unbound last-stock:] symbol repend stocks [symbol price] ] ... ]

Ladislav
7-Oct-2006 6:33:23
Or, using my BUILD (a shameless plug) and the UNBIND function:

    mod: make module! build/with [
        [
            title: "Stock Handler"
            module: 'mod-stocks
        ]

unbind* [stocks:] [] unbind* [buy:] func [symbol price] [ unbind* [last-stock:] symbol repend stocks [symbol price] ] ... ] [unbind*: :unbind]

Gabriele
7-Oct-2006 6:40:10
Volker: you don't need export: all because that is the default.

Ladislav: I'd guess that last-stock is not exported because it is not a set-word. (i.e. like with objects, there is a scan for set-words, and those are exported by default.) About making modules dynamically, I think that having the unbind function should be enough. (Maybe unbind/except if we want the ability to "import" words.)

Ladislav
7-Oct-2006 9:28:22
Questions: 1) are modules only about set-words as Gabriele suggests? 2) don't you think, that UNBIND/ONLY can be more convenient than UNBIND/EXCEPT?
Ladislav
7-Oct-2006 9:49:30
1) it looks to me, that the object like localization is insufficient for modules

2) Gabriele is right and UNBIND/EXCEPT is safer

-pekr-
7-Oct-2006 10:42:31
I am not a rebol expert, especially in bind, context, reflectivity areas, but I am not sure I like the difference of module being instantiated dynamically vs. loaded from file. The description already looks complicated and I am not sure if it will not confuse ppl. It should be "simple and obvious". After all - what is "module loaded from file"? Let's think about persistent storage - is it a "file", or is it just a "section of memory" for your script?

I am also confused a bit here, as I thought some ppl (Ladislav, Gabriele) are more involved in R3 design, and I can see Ladislav being surprised here by the blog article. I hope that final solution will be really elegant, and will not complicate rebol too much :-)

-pekr-

Carl Sassenrath
7-Oct-2006 13:05:56
Yes, Gabriele is correct.

Part of the issue is that REBOL V3 exposes another layer to the process of binding that in V2 is hidden.

Essentially, you can treat any module as if *all* of its words (including system functions, etc.) are encapsulated into a box. They stand alone -- their own "world".

The next step is to determine (either with specification or wise defaults such as that suggested above) the relationship of those words to the current environment (ie. the chain of contexts, such as system functions, special "libraries", etc.)

The process of preparing code for evaluation involves more than just binding, in must include a means of determining the *definitions* for the main words of the environment.

In the example given above, the *entire* run-time context for the code is about 20 words. The other 500 words of REBOL do not need to be part of that context, so as a result the context of that script is much smaller in its memory usage.

Carl Sassenrath
7-Oct-2006 13:27:53
The example is also meant to point out the fact if a module is created from existing code (an existing context), it must be "internalized" (or "interned" to borrow a Lisp word) differently.

The process is quite simple and consistent with the overall design of REBOL. I think it will become more clear once I publish a diagram of it.

It is important to realize that there exists four domains (for the existence) of REBOL code:

1. The "raw" source code as text (the serialized external representation)

2. The "raw" loaded blocks (the serialized internal representation).

3. The loaded blocks along with their "independent context". Words are BOUND, but not RESOLVED.

4. The "interned" code and its "interdependent context". At this point, the code is ready to be evaluated. It is not only BOUND, it is also RESOLVED (the values of the top level word have been mapped over from other contexts).

There is actually a 5th domain: a binary serialized external form, REBin, but it is not part of the normal process, it is a replacement for #1. But more on that later; I've already put too much into this comment posting.

Carl Sassenrath
7-Oct-2006 13:38:04
One final comment. I'm describing the basic process here - the foundation; however, there exists a wide variety of "specification options" that we can apply to the mapping of contexts.

For example, Volker mentions [Export: all] and that is exactly what I mean. You can also imagine [Export: none], and perhaps [Export: set-words], etc. And, that's just the export options! There is also import, as well as specifications of the word values themselves (e.g. datatype restrictions, read only, etc.)

So, the trick will be to select the smartest choices for those options. We will do this by making the best first guess for the Alpha release, then start trying real code to come up with what makes the most sense.

Ladislav
7-Oct-2006 14:43:14
The problem was, that the code like:
mod: make module! [
    [
        title: "Stock Handler"
        module: 'mod-stocks
        export: [buy sell]
        import: [func repend if not remove-all =]
    ]

stocks: []

buy: func [symbol price] [ last-stock: symbol repend stocks [symbol price] ]

sell: func [symbol price] [ if not symbol [symbol: last-stock] remove-all [asymbol aprice] stocks [ symbol = asymbol ] ] ]

would be more understandable to me, because it specifies everything which may otherwise be unclear.
Brian Hawley
7-Oct-2006 19:38:10
This sort-of makes sense to me, assuming I'm not way off.

The exported words of the modules you import are visible in your module, but in a copy-on-write sort of way. If you set any words (even external ones) while you are in your module's context those words are set in your module's context rather than their original.

This would mean that imported words are given a kind of special two-level binding, local and original. If there is a local setting for that word it is used, otherwise use the original.

I take back what I said earlier - I am likely way off.

Ladislav
12-Oct-2006 20:45:42
The question is, whether I understood the articles correctly, and the main difference between the dynamically created modules and script modules is, that in addition to the imported words ("created module" words initialized with values taken from the "creating module") and exported words ("created module" words used to "set back" the words in the "creating module" after the "module creation") the dynamically created modules may use shared words (words not rebound to the "created module"). As I see it, we may be able to simulate "word sharing" in script modules by using something like

shared:

header item to tell we want to "share" the specified words from the "creating module" instead of defining them in the "created module"?

Frank
15-Jan-2007 3:18:15
I wonder how the Interpreter will know, what module to bind a new word to.

Using a code like:

mod1: make module! [ .. blah blah ..

test: func [/local word] [ word: to-word "MyWord" ] ]

How will the Interpreter know that 'MyWord should be bound to mod1?

I think there are three ways this could be done:

- The interpreter could keep track what function it is running at the moment. When writing mod1/test. It could but mod1 into a stack. But this would be problematic when trying to do something like

test2: get in mod1 'test

But maybe this isn't allowed anyway. All words would change there bindings when moved to an other module. This could be a performance problem, too.

- The interpreter could annotate the module to all functions. This would be problematic for this case:

mytest: [to-word "MyWord"] test: func [] [do mytest]

- The interpreter could annotate the module to all (any-)blocks. This would be problematic when trying to share blocks between modules. An other Module could append words to create words that are bound to other Module.

Post a Comment:

You can post a comment here. Keep it on-topic.

Name:

Blog id:

R3-0048


Comment:


 Note: HTML tags allowed for: b i u li ol ul font span div a p br pre tt blockquote
 
 

This is a technical blog related to the above topic. We reserve the right to remove comments that are off-topic, irrelevant links, advertisements, spams, personal attacks, politics, religion, etc.

REBOL 3.0
Updated 13-Dec-2024 - Edit - Copyright REBOL Technologies - REBOL.net