[REBOL] Re: Context - code included- 2nd version
From: holger:rebol at: 12-Sep-2001 17:39
On Wed, Sep 12, 2001 at 11:26:57PM +0200, Ladislav Mecir wrote:
> The latest version of http://www.sweb.cz/LMecir/contexts.html reflects some
> points made in the latest discussions.
Unfortunately your major misconceptions are still in there. The highlights:
- Words do not "store" values, they ARE values. REBOL is a first-class language,
and a word is a value like any other value. Values may have properties that
distinguish different values of the same type from each other, e.g. integer!
values may have different numeric values. The properties of word! (and lit-word!,
set-word!, get-word!) are, precisely, a reference to the context the word is bound
to (if any) and its spelling.
When evaluating a value the result is, usually, another value. The value you get
when evaluating a word is NOT a property of the word, it is something determined
dynamically by the interpreter, at the time the word is evaulated. Those values are
not stored in words, they are stored in contexts.
- Contexts are not word tables, but name-value lookup tables. The "names" are
the spellings of words. The "values" are ordinary REBOL values, and are what
you get when you evaluate a word with the same spelling that is bound into
the context.
- There is no "Special Context" and there are no "Special Words". What you call
a "Special Word" is in reality a word not bound into a context.
- Your distinction between local, global, special etc. words is a little
confusing, mostly because "Loaded Word" is orthogonal to the other definitions.
It refers to the existance of a word in the global context, whereas all the
other definitions refer to the binding of an individual word.
Better, more consistent with the documentation (not to mention implementation):
Replace "Special Word" with "word not bound into a context".
Replace "Loaded Word" with "word which exists in the global context".
Replace "Global Word" with "word bound into the global context".
Replace "Local Word" with "word bound into a non-global context".
These are the major problems with your document. A lot of the complexity and many
of the individual observations you made seem interesting and significant only because
your somewhat arbitrary definitions obscure the actual meaning. Once those terms
are replaced by their meanings most observations follow logically and become
redundant.
Some more minor nitpicks:
- to-block! does not bind words into a context. make word! and to-word
bind all words into the global context (terminology).
- The global context expands automatically as new words are bound into it.
The fact that this increases the number of "Loaded Words" is redundant
(again, result of obscure terminology).
- Aliases are not properties of contexts and therefore do not show up in
visualizations of contexts. That's because aliases are global, not local
per context.
- Scoping: REBOL uses lexical scoping, not dynamic scoping, but allows words
to be rebound during block evaluation. That's how words can refer to different
contexts even though their scope is lexical. "Scope hierarchies" imply some
kind of tree, which does not exist, so using that term is probably a bad idea.
Calling a scope hierarchy "virtual" is yet another definition which obscures
facts rather than illustrates them.
The facts are: words live inside of blocks. When a block is created all words
in it are either bound into a context, or remain unbound. Certain functions
('use etc.) change the binding of words. That is all there is to it, and the
"scope" of a word at any given time is simply determined by the last bind
operation on it before the word gets evaluated.
- The "bug" you described when combining 'use with functions and other contexts
is not a bug. It is intentional. There are only four ways how a function can
(theoretically) access its local variables and arguments, in any language:
- Using a hidden context pointer. That's what C does, using the stack pointer.
REBOL does not (and cannot) do it, because the only local environment REBOL
has when executing a function body is that particular function body, which is
a simple block, and the association from a function to its block is one-way (like
all references in REBOL). This means the function body has no hidden reference
back to the function itself, and therefore cannot access its arguments.
- By creating a completely new "environment" for each function. That includes
a new argument and variable space (context) AND a new function body (deep-copied).
This way all words in the function body can be rebound into the new context.
That's what your 'cfunc does, but it is unacceptable performance-wise.
- By keeping a single function body for all function invocations, and dynamically
rebinding it to a different context, one for each invocation, every time a
function is called or returns. This generates O(n) complexity for each function
call, where n is the size of the function, i.e. performance is prohibitive as
well for large functions, and it has other practical problems.
- By keeping a single function body for all function invocations, bound to the
same context, and changing the context on the fly (i.e. exchanging the
"value" part of the name-value table for each function call, using a virtual
stack). This is what REBOL does. The "bug" you describe is a normal and
inevitable side effect of this, and not a "bug" at all. Your "fix" changes the
function execution model in a way that generates unacceptable performance.
If you really need the behavior you describe then you are, of course, free to
write your own functions which implement it, as you have done, but for performance
reasons that behavior should not be the default in REBOL.
- The drawback you describe in regard to what you call the Dynamic Recursion Path
is actually caused by the fact that the context of the outer function remains
intact (indefinite extent) AND retains its values after the outer function
returns (for non-recursive function invocations), under certain circumstances.
This is an implementation detail you should not rely on, because it is likely to
change in future versions, i.e. there is a good chance that in the future after
a (non-recursive) function invocation the values of that function's context are
set to unset!. This would break the way your inner function references the
outer function's arguments after the outer function has returned.
The problem with function contexts keeping their values after the function returns
is that the referenced values do not get garbage-collected, even though no (apparent)
reference exists any more, until the function is called again. In some situations
(e.g. when trying to close and garbage-collect an unreferenced port) this can be very
problematic.
Relying on the values of a function's context beyond the life time of the corresponding
function call should be considered illegal, i.e. you need to, e.g., 'reduce the body
of your
inner function so the argument gets replaced by its value before the function is returned.
This also resolves the drawback you mentioned, i.e. use
f: func [x] [func [] reduce [x]]
and the behavior is as expected and compatible with future versions.
--
Holger Kruse
[holger--rebol--com]