Anthony,
I agree World is dynamic, especially in a multi-user system. But I would use the execution stack as the dynamic scope, not a sandbox/island. World would be obtained by raising a signal. Some context up the sender chain would respond to it and return it. This implies that some object that initiated the thread knows which World to act on, which seems appropriate for user actions and even the World stepping loop which would have an instance variable pointing to the actual active World.
So I think both static scope (pools) and dynamic scope (contexts) are appropriate. The significance is dynamic scope is not an extra concept like islands. I believe this is more in line with the objects-as-capabilities concept.
Have you looked at RuntimeEnvironments (on SqueakMap)? They accomplish the same effect, but they are cleaner than using exceptions...they also work when a process is forked. To do what you describe would look like:
---- Env clamp: [ Env at: #world put: aWorld. self doSomethingThatRequiresAWorld. ]. ---- SomeClass>>doSomethingThatRequiresAWorld
(Env at: #world) doSomething. ----
A Morphic package could also extend the runtime environment accessor to include #world and #world: methods to make the above even cleaner looking.
- Stephen
On Mon, 27 Jan 2003, Stephen Pair wrote:
Have you looked at RuntimeEnvironments (on SqueakMap)? They accomplish the same effect, but they are cleaner than using exceptions...
By whose definition of cleaner? ;)
I find using exceptions preferable for at least two reasons:
- the semantics of dynamic scope are defined by the context stack, not by Process. There is not always a one-one relationship here. If you use a dynamic scoping mechanism that walks the context stack, you get the correct behavior for free when working with constructs like Continuation. RuntimeEnvironments would require special treatment to work across coroutining or continuation invocation.
- a mechanism based purely on exceptions is likely to be fairly portable, and also likely to make good use of primitives
Avi
Avi Bryant wrote:
On Mon, 27 Jan 2003, Stephen Pair wrote:
Have you looked at RuntimeEnvironments (on SqueakMap)? They accomplish the same effect, but they are cleaner than using exceptions...
By whose definition of cleaner? ;)
Well, I consider "Env at: #world" to be more clear about the intent than "MyWorldEnvironment signal" or similar...don't you? I think you are looking at the implementation.
I find using exceptions preferable for at least two reasons:
- the semantics of dynamic scope are defined by the context
stack, not by Process. There is not always a one-one relationship here.
I agree, but you're speaking about implementation.
If you use a dynamic scoping mechanism that walks the context stack, you get the correct behavior for free when working with constructs like Continuation. RuntimeEnvironments would require special treatment to work across coroutining or continuation invocation.
Yes, your implementation of Continuation has made that clearly evident to me. ;)
I agree that the runtime environment should be inter-woven in the context stack. I'm actually working on that change right now.
- a mechanism based purely on exceptions is likely to be
fairly portable, and also likely to make good use of primitives
Agreed.
- Stephen
On Mon, 27 Jan 2003, Stephen Pair wrote:
Avi Bryant wrote:
On Mon, 27 Jan 2003, Stephen Pair wrote:
Have you looked at RuntimeEnvironments (on SqueakMap)? They accomplish the same effect, but they are cleaner than using exceptions...
By whose definition of cleaner? ;)
Well, I consider "Env at: #world" to be more clear about the intent than "MyWorldEnvironment signal" or similar...don't you? I think you are looking at the implementation.
Well, yeah. :) I thought that's what you meant. Obviously there's no reason you can't have the same interface to both implementation approaches. I currently use an exception based dynamic scope that looks like
DynamicContext bind: #foo to: 42 during: [ self assert: #foo dynamicValue = 42 ]
self assert: #foo dynamicValue isNil.
But whatever, interface is easy to change.
I agree that the runtime environment should be inter-woven in the context stack. I'm actually working on that change right now.
Cool.
Avi Bryant wrote:
Well, yeah. :) I thought that's what you meant. Obviously there's no reason you can't have the same interface to both implementation approaches. I currently use an exception based dynamic scope that looks like
DynamicContext bind: #foo to: 42 during: [ self assert: #foo dynamicValue = 42 ]
self assert: #foo dynamicValue isNil.
Actually, I rather like the #dynamicValue pattern. Why not this then:
---- DynamicContext clamp: [ #foo dynamicValue: 42. self assert: #foo dynamicValue = 42 ].
self assert: #foo dynamicValue isNil ----
With the above, if you need to set several values, you don't need to enclose your code in several blocks.
- Stephen
On Mon, 27 Jan 2003, Stephen Pair wrote:
Actually, I rather like the #dynamicValue pattern. Why not this then:
DynamicContext clamp: [ #foo dynamicValue: 42. self assert: #foo dynamicValue = 42 ].
self assert: #foo dynamicValue isNil
With the above, if you need to set several values, you don't need to enclose your code in several blocks.
True, but I find the semantics less clear. Say I capture a continuation just before the call to #dynamicValue:, and then come back to it:
|k|
#foo dynamicValue: 0. DynamicContext clamp: [ k := Continuation current. Transcript cr; show: #foo dynamicValue. #foo dynamicValue: 42. ]
k ifNotNil: [k value].
Does this show
0 0
or
0 42
on the Transcript?
If you want to be able to set multiple bindings at once, I would suggest
DynamicContext bindAll: {#foo -> 42} during: [...]
On Mon, 27 Jan 2003, Avi Bryant wrote:
If you want to be able to set multiple bindings at once, I would suggest
DynamicContext bindAll: {#foo -> 42} during: [...]
Stephen, might I also suggest that if you need the feature from RuntimeEnvironments of child processes keeping their parent's dynamic context, rather than special casing it, you do something like this:
BlockContext>>forkWithDynamicContext |context| context := DynamicContext bindings. ^ [DynamicContext bindAll: context during: self] fork
On Mon, 27 Jan 2003, Avi Bryant wrote:
If you want to be able to set multiple bindings at once, I would suggest
DynamicContext bindAll: {#foo -> 42} during: [...]
Stephen, might I also suggest that if you need the feature from RuntimeEnvironments of child processes keeping their parent's dynamic context, rather than special casing it, you do something like this:
BlockContext>>forkWithDynamicContext |context| context := DynamicContext bindings. ^ [DynamicContext bindAll: context during: self] fork
Part of the problem here is a fundamental issue with how forking actually works. In a perfect world, the forked context would point back to the calling context even though the forked process should never return to the calling context. This back pointer should be independent of the return path that the Process follows and would be used for finding unwind blocks, exception handlers, and dynamic context (and dynamic context should probably be used to implement unwind blocks and exception handlers).
But, to keep this discussion grounded in present realities...I'm assuming that #bindings would lookup (up the context stack) and flatten all enclosing bindings into a single dictionary. I think I might actually do better if I have a handler at the top of the forked process that pointed back to the calling context and somehow continued the lookup there. Unfortunately, I think I might run into some limitations of the Exception handling system and may need to use the underlying primitives directly.
One of the features of RuntimeEnvironments is that you can create a tree of environments that is maintained independent of any process or stack (these are instances of SharedRuntimeEnvironment). Any node in that tree can then be assigned as the environment for a given process. If a process manipulates it's environment, changes are only seen in that process, however, if you directly access one of your SharedRuntimeEnvironments, changes are visible to all processes using that runtime environment (either directly, or indirectly through a child). In this way, I can have pools of processes whose dynamic values for certain variables are always in sync with each other and their SharedRuntimeEnvironment (unless a given process overrides a variable).
I could possible mimick this with an exception/context stack implementation if I had a context at the top of each stack that pointed to a SharedRuntimeEnvironment. I suppose that if such a context is not there, I could just default to an image wide default environment.
- Stephen
What is not clear about those semantics? Since the transcript output occurs before setting the value, the correct output is:
0 0
Originally, I wanted the ability set a value for all subsequent operations that occur in a process...however, to accomplish that *and* use the stack for maintaining scope enclosures would be quite convoluted (if at all possible). And, it's also not clear to me at this point whether or not I really need that ability.
- Stephen
-----Original Message----- From: squeak-dev-bounces@lists.squeakfoundation.org [mailto:squeak-dev-bounces@lists.squeakfoundation.org] On Behalf Of Avi Bryant Sent: Monday, January 27, 2003 3:29 PM To: The general-purpose Squeak developers list Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = Squeak x Kernel-E)
On Mon, 27 Jan 2003, Stephen Pair wrote:
Actually, I rather like the #dynamicValue pattern. Why not
this then:
DynamicContext clamp: [ #foo dynamicValue: 42. self assert: #foo dynamicValue = 42 ].
self assert: #foo dynamicValue isNil
With the above, if you need to set several values, you
don't need to
enclose your code in several blocks.
True, but I find the semantics less clear. Say I capture a continuation just before the call to #dynamicValue:, and then come back to it:
|k|
#foo dynamicValue: 0. DynamicContext clamp: [ k := Continuation current. Transcript cr; show: #foo dynamicValue. #foo dynamicValue: 42. ]
k ifNotNil: [k value].
Does this show
0 0
or
0 42
on the Transcript?
If you want to be able to set multiple bindings at once, I would suggest
DynamicContext bindAll: {#foo -> 42} during: [...]
On Mon, 27 Jan 2003, Stephen Pair wrote:
What is not clear about those semantics? Since the transcript output occurs before setting the value, the correct output is:
0 0
Well, unclear in that there's a conflict between what I think the correct semantics should be (which we agree about), and the way in which it's likely to be implemented ;). In other words: take that example as a test case, and make sure it passes; it will probably force you into implementation strategies you wouldn't otherwise use.
Originally, I wanted the ability set a value for all subsequent operations that occur in a process...however, to accomplish that *and* use the stack for maintaining scope enclosures would be quite convoluted (if at all possible).
I need the same thing, and yes, it's pretty near impossible (short of inserting dummy contexts at the root of the stack). For that kind of state, I do keep a separate, Process-keyed tree, and the interface looks like
s := StateHolder new. s contents: 'foo'. [self assert: s contents isNil] fork. self assert: s contents = 'foo'.
I won't show it here, but the nice part is that you can cheaply snapshot and restore the current value of all StateHolders in the Process at once - which is necessary for web-browser backtracking.
It may be worthwhile to release Continuation, DynamicContext, and StateHolder together as a kind of "obscure control flow" package (along with my implementation of McCarthy's "amb", which is trippier than any of them). They're also the litmus test for Seaside portability - if you can get all of their tests passing in a particular dialect, porting Seaside will be a snap.
Avi
What is not clear about those semantics? Since the
transcript output
occurs before setting the value, the correct output is:
0 0
Well, unclear in that there's a conflict between what I think the correct semantics should be (which we agree about), and the way in which it's likely to be implemented ;). In other words: take that example as a test case, and make sure it passes; it will probably force you into implementation strategies you wouldn't otherwise use.
Like not using the context stack for example? It's trivial to get Continuation to do the correct thing with RuntimeEnvironments (as they are currently implemented). When you create the continuation, you just need to tell the saved environment to beShared. Subsequent modification of the environment will then be isolated.
Originally, I wanted the ability set a value for all subsequent operations that occur in a process...however, to accomplish
that *and*
use the stack for maintaining scope enclosures would be quite convoluted (if at all possible).
I need the same thing, and yes, it's pretty near impossible (short of inserting dummy contexts at the root of the stack). For that kind of state, I do keep a separate, Process-keyed tree, and the interface looks like
s := StateHolder new. s contents: 'foo'. [self assert: s contents isNil] fork. self assert: s contents = 'foo'.
I won't show it here, but the nice part is that you can cheaply snapshot and restore the current value of all StateHolders in the Process at once - which is necessary for web-browser backtracking.
So remind me again why it's desirable to reuse the context stack for runtime environments (and ultimately exceptions for that matter)? I'm starting to doubt that it is. Yes, there are some things which are simplified...but I think I might be squeezing myself into a straight jacket because it happens to feel warm and cuddly.
It might however be much cleaner to put the environment pointer in ContextPart rather than in Process (since Process is really just holding onto the environment for the active context). But if I go that route, I'll be fighting with the VM.
It may be worthwhile to release Continuation, DynamicContext, and StateHolder together as a kind of "obscure control flow" package (along with my implementation of McCarthy's "amb", which is trippier than any of them). They're also the litmus test for Seaside portability - if you can get all of their tests passing in a particular dialect, porting Seaside will be a snap.
I'll have to read up an "amb"...but trippy should not be goal. ;)
BTW, the fact that continuations re-use processes threw me for a loop at first. When I was doing the stuff for Swiki.net, I didn't re-use processes, I just created new ones...it seemed a lot easier to follow the control flow.
For example, when you evaluate a continuation, why not have the continuation happen in a separate process? I about twisted my brain in a knot when I tried to figure out how it worked to call a continuation from the UIProcess (which I didn't think would work)...that is until I realized that it in fact didn't work. It only fooled me into thinking that it worked because the continuation was originally created from the UIProcess. If you create a continuation in a workspace from within a forked block and then later open an inspector on that continuation and send it the #value message, you'll hose your UIProcess. That's a devilish trick to pull on an unsuspecting victim. ;)
I've been thinking of ways to clean up Continuation a bit such that it eliminates such freakish behavior. I think this might have the pleasurable side effect of making Seaside's use of continuations a bit easier to follow.
- Stephen
On Mon, 27 Jan 2003, Stephen Pair wrote:
So remind me again why it's desirable to reuse the context stack for runtime environments (and ultimately exceptions for that matter)? I'm starting to doubt that it is. Yes, there are some things which are simplified...but I think I might be squeezing myself into a straight jacket because it happens to feel warm and cuddly.
I don't know about "runtime environments", because I don't know what your goals are. But dynamic scope should be implemented with the context stack because that's how dynamic scope is defined. One thing working with continuations and other non-standard control flow techniques does is force you to be clear about what kind of semantics you need for what. I have a definite use for a scoping mechanism wherein a binding's value is resolved by looking up the execution stack. Yes, this could be implemented in some way other than looking through the sender chain, but then you have to be careful that whenever somebody manipulates the stack, you correspondingly adjust your bindings - and it's just too easy for this to go out of sync. If exceptions, for example, were implemented in some way other than by walking the stack, I would have to muck about with the exception system every time I invoked a Continuation...
Now, there are other times that I need other semantics, but for those times I don't use dynamic scope.
I'll have to read up an "amb"...but trippy should not be goal. ;)
Amb was done for fun, and as a mind-bending demo of continuations. I've never actually used it, although I imagine it might be quite useful for parsers and similar problems. The simplest example is probably something like:
|amb x y| amb := Amb new. x := amb oneOf: (1 to: 10). y := amb oneOf: (1 to: 10). amb assert: (x * y) = 42. Transcript cr; show: {x. y}.
This will print
#(6 7)
to the Transcript.
BTW, the fact that continuations re-use processes threw me for a loop at first. When I was doing the stuff for Swiki.net, I didn't re-use processes, I just created new ones...it seemed a lot easier to follow the control flow.
For example, when you evaluate a continuation, why not have the continuation happen in a separate process?
I think the implementation would be a lot more complicated, and I don't see how it would be useful. If you want the continuation to happen in a separate process, just fork before you evaluate it.
I've been thinking of ways to clean up Continuation a bit such that it eliminates such freakish behavior. I think this might have the pleasurable side effect of making Seaside's use of continuations a bit easier to follow.
I doubt it ;).
Avi
Avi Bryant wrote:
On Mon, 27 Jan 2003, Stephen Pair wrote:
So remind me again why it's desirable to reuse the context
stack for
runtime environments (and ultimately exceptions for that
matter)? I'm
starting to doubt that it is. Yes, there are some things which are simplified...but I think I might be squeezing myself into a
straight
jacket because it happens to feel warm and cuddly.
I don't know about "runtime environments", because I don't know what your goals are. But dynamic scope should be implemented with the context stack because that's how dynamic scope is defined.
Perhaps someone at some point defined it that way, but that doesn't necessarily mean it's the best definition.
One thing working with continuations and other non-standard control flow techniques does is force you to be clear about what kind of semantics you need for what.
Agreed.
I have a definite use for a scoping mechanism wherein a binding's value is resolved by looking up the execution stack. Yes, this could be implemented in some way other than looking through the sender chain, but then you have to be careful that whenever somebody manipulates the stack, you correspondingly adjust your bindings - and it's just too easy for this to go out of sync.
But that's only in the implementation where the dynamic bindings are attached to the process...if they were attached to, but distinct from the context stack, then I think you could get the added flexibility and the better design of not having the process hold the dynamic bindings (and this is where I'm now going with RE). Lookup would use an exception to discover the nearest environment, and from there it would lookup a separate stack of environments. Clamping would simply find the nearest environment and create a new isolation. A forked process would still need to have an enclosing handler that linked it to the current environment of the calling context. Stacks that have no context that defines an environment will use an image-wide default environment.
Here's another test case...what would you expect the following to output (assuming that dynamic context were handled across forks)?
---- | s | s := Semaphore new. Env at: #test put: '1'. [ Transacript show: (Env at: #test); space. s signal. s wait. Transacript show: (Env at: #test); space. ] fork.
s wait. Env at: #test put: '2'. s signal. ----
1 1
or:
1 2
Since there is no explicit isolation (like the use of a #clamp: method), I would expect them to share an environment and thus output "1 2 " (note: RE currently outputs "1 1 " because I force the environments to be isolated when forking, but I now think that's incorrect).
If exceptions, for example, were implemented in some way other than by walking the stack, I would have to muck about with the exception system every time I invoked a Continuation...
But think about what it would mean if you had a completely different mechanism for branching off processing (other than the standard fork)...think about an asynchronous or eventual sending scenario where your caller may be long expired (and your sender nilled) by the time you actually need to lookup an exception handler or some dynamic binding. The same thing happens when forking a process, it's just that it's done so rarely in today's system that it's easy to gloss over that issue. If you could maintain a caller chain that's maintained even after a context returns, then the current exception handling might work under such circumstances, but in today's system, it completely falls apart.
Now, there are other times that I need other semantics, but for those times I don't use dynamic scope.
I'll have to read up an "amb"...but trippy should not be goal. ;)
Amb was done for fun, and as a mind-bending demo of continuations. I've never actually used it, although I imagine it might be quite useful for parsers and similar problems. The simplest example is probably something like:
|amb x y| amb := Amb new. x := amb oneOf: (1 to: 10). y := amb oneOf: (1 to: 10). amb assert: (x * y) = 42. Transcript cr; show: {x. y}.
This will print
#(6 7)
to the Transcript.
BTW, the fact that continuations re-use processes threw me
for a loop
at first. When I was doing the stuff for Swiki.net, I
didn't re-use
processes, I just created new ones...it seemed a lot easier
to follow
the control flow.
For example, when you evaluate a continuation, why not have the continuation happen in a separate process?
I think the implementation would be a lot more complicated, and I don't see how it would be useful. If you want the continuation to happen in a separate process, just fork before you evaluate it.
Yes, perhaps it is too much to assume that you want it evaluated in another process...but I'd say that in most cases you *should* put it into another process (if for no other reason than to preserve the sanity of people trying to read your code). Perhaps the standard #value should do the fork for you and have a bit more explicit #jumpInto or something similar that would give you some indication that you're about to jumple the active context stack. But then again, maybe Semaphores are just too ingrained in psyche and I just need to unlearn them a bit.
As I see it, the evaluation of a continuation is just like signaling a semaphore except that you control which stack gets activated and you have this added side effect of immediately terminating the current process (with no unwinding of contexts mind you). I'm just not convinced that this is a good thing.
I've been thinking of ways to clean up Continuation a bit
such that it
eliminates such freakish behavior. I think this might have the pleasurable side effect of making Seaside's use of
continuations a bit
easier to follow.
I doubt it ;).
Maybe not, but I've seen it done both ways. ;)
- Stephen
Have you considered something like:
a) thread (st-process) local pool (shared) variable
X
b) MyContextCollection withContextDo: valuable X := Array with: self with: X. ^valuable ensure: "finally:" [ X := X at: 2. ].
This assumes a mechanism for supporting per/process (thread-local) shared variables.
In SmallScript this could be written as:
Class name: MyContextCollection shared-fields: X "obviously choosing a better name here" { Method [ withContextDo: valuable X := {self, X}. "optionally, various exception guards could go here to handle resolution via exceptions, etc" ^valuable finally: [X := X[2]]. ] Method [ resolve: expr |tuple| := X. [tuple] whileNotNil: [ tuple[1].internalQuery(expr) !? [:r| ^r]. tuple := tuple[2]. ]. ^nil. "indicate failure" ] }
-- Dave S.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SmallScript for the AOS & .NET Platforms David.Simmons@SmallScript.com http://www.smallscript.org
-----Original Message----- From: squeak-dev-bounces@lists.squeakfoundation.org [mailto:squeak-dev- bounces@lists.squeakfoundation.org] On Behalf Of Stephen Pair Sent: Monday, January 27, 2003 1:03 PM To: 'The general-purpose Squeak developers list' Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = Squeak x Kernel-E)
What is not clear about those semantics? Since the transcript output occurs before setting the value, the correct output is:
0 0
Originally, I wanted the ability set a value for all subsequent operations that occur in a process...however, to accomplish that *and* use the stack for maintaining scope enclosures would be quite convoluted (if at all possible). And, it's also not clear to me at this point whether or not I really need that ability.
- Stephen
-----Original Message----- From: squeak-dev-bounces@lists.squeakfoundation.org [mailto:squeak-dev-bounces@lists.squeakfoundation.org] On Behalf Of Avi Bryant Sent: Monday, January 27, 2003 3:29 PM To: The general-purpose Squeak developers list Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = Squeak x Kernel-E)
On Mon, 27 Jan 2003, Stephen Pair wrote:
Actually, I rather like the #dynamicValue pattern. Why not
this then:
DynamicContext clamp: [ #foo dynamicValue: 42. self assert: #foo dynamicValue = 42 ].
self assert: #foo dynamicValue isNil
With the above, if you need to set several values, you
don't need to
enclose your code in several blocks.
True, but I find the semantics less clear. Say I capture a continuation just before the call to #dynamicValue:, and then come back to it:
|k|
#foo dynamicValue: 0. DynamicContext clamp: [ k := Continuation current. Transcript cr; show: #foo dynamicValue. #foo dynamicValue: 42. ]
k ifNotNil: [k value].
Does this show
0 0
or
0 42
on the Transcript?
If you want to be able to set multiple bindings at once, I would suggest
DynamicContext bindAll: {#foo -> 42} during: [...]
Oops, my bad. I forgot to add the "tls" (thread-local-storage) qualifier to the <X> shared-field declaration.
Class name: MyContextCollection shared-fields: tls X; "obviously choosing a better name here"
{ Method [ withContextDo: valuable X := {self, X}. "optionally, various exception guards could go here to handle resolution via exceptions, etc" ^valuable finally: [X := X[2]]. ] Method [ resolve: expr |tuple| := X. [tuple] whileNotNil: [ tuple[1].internalQuery(expr) !? [:r| ^r]. tuple := tuple[2]. ]. ^nil. "indicate failure" ] }
-- Dave S. [SmallScript Corp]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SmallScript for the AOS & .NET Platforms David.Simmons@SmallScript.com http://www.smallscript.org
-----Original Message----- From: David Simmons [mailto:david.simmons@smallscript.com] Sent: Monday, January 27, 2003 3:57 PM To: 'The general-purpose Squeak developers list' Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = Squeak x Kernel-E)
Have you considered something like:
a) thread (st-process) local pool (shared) variable
X
b) MyContextCollection withContextDo: valuable X := Array with: self with: X. ^valuable ensure: "finally:" [ X := X at: 2. ].
This assumes a mechanism for supporting per/process (thread-local) shared variables.
In SmallScript this could be written as:
Class name: MyContextCollection shared-fields: X "obviously choosing a better name here" { Method [ withContextDo: valuable X := {self, X}. "optionally, various exception guards could go here to handle resolution via exceptions, etc" ^valuable finally: [X := X[2]]. ] Method [ resolve: expr |tuple| := X. [tuple] whileNotNil: [ tuple[1].internalQuery(expr) !? [:r| ^r]. tuple := tuple[2]. ]. ^nil. "indicate failure" ] }
-- Dave S.
SmallScript for the AOS & .NET Platforms David.Simmons@SmallScript.com http://www.smallscript.org > -----Original Message----- > From: squeak-dev-bounces@lists.squeakfoundation.org [mailto:squeak-dev- > bounces@lists.squeakfoundation.org] On Behalf Of Stephen Pair > Sent: Monday, January 27, 2003 1:03 PM > To: 'The general-purpose Squeak developers list' > Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = Squeak x Kernel- E) > > What is not clear about those semantics? Since the transcript output > occurs before setting the value, the correct output is: > > 0 > 0 > > Originally, I wanted the ability set a value for all subsequent > operations that occur in a process...however, to accomplish that *and* > use the stack for maintaining scope enclosures would be quite convoluted > (if at all possible). And, it's also not clear to me at this point > whether or not I really need that ability. > > - Stephen > > > -----Original Message----- > > From: squeak-dev-bounces@lists.squeakfoundation.org > > [mailto:squeak-dev-bounces@lists.squeakfoundation.org] On > > Behalf Of Avi Bryant > > Sent: Monday, January 27, 2003 3:29 PM > > To: The general-purpose Squeak developers list > > Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = > > Squeak x Kernel-E) > > > > > > > > On Mon, 27 Jan 2003, Stephen Pair wrote: > > > > > Actually, I rather like the #dynamicValue pattern. Why not > > this then: > > > > > > ---- > > > DynamicContext clamp: [ > > > #foo dynamicValue: 42. > > > self assert: #foo dynamicValue = 42 > > > ]. > > > > > > self assert: #foo dynamicValue isNil > > > ---- > > > > > > With the above, if you need to set several values, you > > don't need to > > > enclose your code in several blocks. > > > > True, but I find the semantics less clear. Say I capture a > > continuation just before the call to #dynamicValue:, and then > > come back to it: > > > > |k| > > > > #foo dynamicValue: 0. > > DynamicContext clamp: [ > > k := Continuation current. > > Transcript cr; show: #foo dynamicValue. > > #foo dynamicValue: 42. > > ] > > > > k ifNotNil: [k value]. > > > > Does this show > > > > 0 > > 0 > > > > or > > > > 0 > > 42 > > > > on the Transcript? > > > > If you want to be able to set multiple bindings at once, I > > would suggest > > > > DynamicContext bindAll: {#foo -> 42} during: [...] > > > > > > > > > > >
Actually, thread-local storage was the original motivation for RuntimeEnvironments (although I hadn't considered allowing pool variables to be declared in that way). It quickly became apparent that tls and dynamic scoping is really the same thing. Thread local storage implies that the storage is associated with the thread, and that's the main reason that RuntimeEnvironments currently reside in Process. However, I think we do need something that is stack based because we really cannot see what lies ahead in terms of processes and scheduling (Continuations is a great example in this regard; the debugger is yet another). Putting things directly into Process or keying them off of Process can make the solution unnecessarily brittle.
Currently, my basic argument is that while we should use the context stack to access the dynamic context, the dynamic varaible stack and the call/return context stack should not be inter-twined because the call/return mechanism can restrict what you are able to with dynamic variable binding. I'll give two examples:
A) You want to set some dynamic variable for everything that follows temporally (including forked processes)
B) You want a called context's dynamic bindings to not be affected by direct manipulation of the dynamic bindings in a calling context (if you do a simple parent traversal based on the context stack, you cannot achieve that effect). This is paricularly important for continuations because contexts can be copied and have multiple activations...unless the continuation framework is careful about copying dynamic bindings when it copies the stack, you can get yourself into a mess here.
I could also imagine a VM that might do away with Process and schedules contexts independently. Instead of #fork, you might have an asynchronous message send bytecode. The question in such a system is how do you maintain dyanmic context (and exception handlers are a form of dynamic context) during such asynchronous message sends? For example (assuming that async means send asynchronously):
---- SomeClass>>someMethod
[self async doSomething] on: Error do: [ :ex | self handleException ].
----
Would you expect the handler to get evaluated if an exception happened in #doSomething? Even if #doSomething is happening asynchronously? This would roughly equate to:
---- SomeClass>>someMethod
[[self doSomething] fork] on: Error do: [ :ex | self handleException ]. ----
Which of course would not handle an error that occurs in #doSomething...but should it? If so, then you can't rely on the context stack alone to provide you with the exception handler contexts because handler context may go out of scope as processing handling context proceeds in parallel with the processing of #doSomething.
I wonder if there has been anything written in past on this specific case? Ignoring the implementation and limitations of current exception handling systems, wouldn't it make sense for these exception handlers be in effect even in the presence of a forked process?
- Stephen
-----Original Message----- From: squeak-dev-bounces@lists.squeakfoundation.org [mailto:squeak-dev-bounces@lists.squeakfoundation.org] On Behalf Of David Simmons Sent: Monday, January 27, 2003 7:01 PM To: 'The general-purpose Squeak developers list' Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = Squeak x Kernel-E)
Oops, my bad. I forgot to add the "tls" (thread-local-storage) qualifier to the <X> shared-field declaration.
Class name: MyContextCollection shared-fields: tls X; "obviously choosing a better name here"
{ Method [ withContextDo: valuable X := {self, X}. "optionally, various exception guards could go here to handle resolution via exceptions, etc" ^valuable finally: [X := X[2]]. ] Method [ resolve: expr |tuple| := X. [tuple] whileNotNil: [ tuple[1].internalQuery(expr) !? [:r| ^r]. tuple := tuple[2]. ]. ^nil. "indicate failure" ] }
-- Dave S. [SmallScript Corp]
SmallScript for the AOS & .NET Platforms David.Simmons@SmallScript.com http://www.smallscript.org > -----Original Message----- > From: David Simmons [mailto:david.simmons@smallscript.com] > Sent: Monday, January 27, 2003 3:57 PM > To: 'The general-purpose Squeak developers list' > Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = Squeak x > Kernel-E) > > Have you considered something like: > > a) thread (st-process) local pool (shared) variable > > X > > b) MyContextCollection > withContextDo: valuable > X := Array with: self with: X. > ^valuable ensure: "finally:" [ > X := X at: 2. > ]. > > This assumes a mechanism for supporting per/process (thread-local) > shared variables. > > In SmallScript this could be written as: > > Class name: MyContextCollection > shared-fields: X "obviously choosing a better name here" { > Method [ > withContextDo: valuable > X := {self, X}. > "optionally, various exception guards could go here > to handle resolution via exceptions, etc" > ^valuable finally: [X := X[2]]. > ] > Method [ > resolve: expr > |tuple| := X. > [tuple] whileNotNil: [ > tuple[1].internalQuery(expr) !? [:r| ^r]. > tuple := tuple[2]. > ]. > ^nil. "indicate failure" > ] > } > > -- Dave S. > > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > SmallScript for the AOS & .NET Platforms David.Simmons@SmallScript.com > http://www.smallscript.org > > > -----Original Message----- > > From: squeak-dev-bounces@lists.squeakfoundation.org > > [mailto:squeak-dev- bounces@lists.squeakfoundation.org] On Behalf Of > > Stephen Pair > > Sent: Monday, January 27, 2003 1:03 PM > > To: 'The general-purpose Squeak developers list' > > Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = Squeak x Kernel- > E) > > > > What is not clear about those semantics? Since the transcript > > output occurs before setting the value, the correct output is: > > > > 0 > > 0 > > > > Originally, I wanted the ability set a value for all subsequent > > operations that occur in a process...however, to accomplish that > > *and* use the stack for maintaining scope enclosures would be quite > > convoluted (if at all possible). And, it's also not clear to me at > > this point whether or not I really need that ability. > > > > - Stephen > > > > > -----Original Message----- > > > From: squeak-dev-bounces@lists.squeakfoundation.org > > > [mailto:squeak-dev-bounces@lists.squeakfoundation.org] On Behalf > > > Of Avi Bryant > > > Sent: Monday, January 27, 2003 3:29 PM > > > To: The general-purpose Squeak developers list > > > Subject: RE: Dynamic scoping (was: Proposal: Squeak-E = Squeak x > > > Kernel-E) > > > > > > > > > > > > On Mon, 27 Jan 2003, Stephen Pair wrote: > > > > > > > Actually, I rather like the #dynamicValue pattern. Why not > > > this then: > > > > > > > > ---- > > > > DynamicContext clamp: [ > > > > #foo dynamicValue: 42. > > > > self assert: #foo dynamicValue = 42 > > > > ]. > > > > > > > > self assert: #foo dynamicValue isNil > > > > ---- > > > > > > > > With the above, if you need to set several values, you > > > don't need to > > > > enclose your code in several blocks. > > > > > > True, but I find the semantics less clear. Say I capture a > > > continuation just before the call to #dynamicValue:, and then come > > > back to it: > > > > > > |k| > > > > > > #foo dynamicValue: 0. > > > DynamicContext clamp: [ > > > k := Continuation current. > > > Transcript cr; show: #foo dynamicValue. > > > #foo dynamicValue: 42. > > > ] > > > > > > k ifNotNil: [k value]. > > > > > > Does this show > > > > > > 0 > > > 0 > > > > > > or > > > > > > 0 > > > 42 > > > > > > on the Transcript? > > > > > > If you want to be able to set multiple bindings at once, I would > > > suggest > > > > > > DynamicContext bindAll: {#foo -> 42} during: [...] > > > > > > > > > > > > > > > > > >
"Stephen Pair" spair@acm.org wrote:
Avi Bryant wrote:
Well, yeah. :) I thought that's what you meant. Obviously there's no reason you can't have the same interface to both implementation approaches. I currently use an exception based dynamic scope that looks like
DynamicContext bind: #foo to: 42 during: [ self assert: #foo dynamicValue = 42 ]
self assert: #foo dynamicValue isNil.
Actually, I rather like the #dynamicValue pattern. Why not this then:
Best of all seems to be to tweak the compiler a little. If we had dynamically-bound global-scope variables, how often would we want to use statically-bound global-scope variables? I can't think of a single case, and switching seems to quietly make some other things work better. For example, switching World to be a dynamic variable will allow multiple worlds to coexist with very little code changes.
In Islands, the syntax is like this:
World "a dynamically-bound variable"
!World "an old-fashioned statically-bound variable. only allowed in rare privilaged methods"
I didn't hit on the wonderful idea of having the compiler send #value; instead, the dynamic variable turned into bytecodes something like this:
!Smalltalk readVariable: #World
Either way should be fine, though. I think you want *some* mechanism to break out, within the method. If nothing else, it is nice to do things like:
aPrivilagedMethod !PrivilagedEnvironment clamp: [ ".. do some privilaged stuff.." ]
But maybe I just haven't thought enough about how to get rid of the last few instances.
Lex
Lex Spoon wrote:
"Stephen Pair" spair@acm.org wrote:
Actually, I rather like the #dynamicValue pattern. Why not
this then:
Best of all seems to be to tweak the compiler a little. If we had dynamically-bound global-scope variables, how often would we want to use statically-bound global-scope variables?
I agree...and probably not very often. I just didn't want to get into compiler hacking until I worked in such a paradigm for a little while.
- Stephen
On Wed, 29 Jan 2003, Lex Spoon wrote:
Either way should be fine, though. I think you want *some* mechanism to break out, within the method. If nothing else, it is nice to do things like:
aPrivilagedMethod !PrivilagedEnvironment clamp: [ ".. do some privilaged stuff.." ]
But maybe I just haven't thought enough about how to get rid of the last few instances.
Lex,
Do you think it's necessary to be able to both statically and dynamically access the same variable names, or simply to have some statically bound variables that only priviledged methods could access? If you really needed both you could have "Smalltalk" (dynamically bound) and "BasicSmalltalk" (statically bound) or some such.
That is, currently there's no problem (or at least not much grumbling) with having an identifier Foo able to be either a class var, or a pool var, or a (static) global; instead of adding new syntax, why not just add dynamic global as a possibility?
Avi
Avi Bryant avi@beta4.com wrote:
On Wed, 29 Jan 2003, Lex Spoon wrote:
Either way should be fine, though. I think you want *some* mechanism to break out, within the method. If nothing else, it is nice to do things like:
aPrivilagedMethod !PrivilagedEnvironment clamp: [ ".. do some privilaged stuff.." ]
But maybe I just haven't thought enough about how to get rid of the last few instances.
Lex,
Do you think it's necessary to be able to both statically and dynamically access the same variable names, or simply to have some statically bound variables that only priviledged methods could access? If you really needed both you could have "Smalltalk" (dynamically bound) and "BasicSmalltalk" (statically bound) or some such.
That is, currently there's no problem (or at least not much grumbling) with having an identifier Foo able to be either a class var, or a pool var, or a (static) global; instead of adding new syntax, why not just add dynamic global as a possibility?
My reasoning was that it is very nice for the static links to be visible when reading the code. Actually, in a first pass, methods could be compiled in either all-static or all-dynamic mode, and I didn't like it. Even when running with all privilages (like being root in Unix), it is nice to have to explicitly ask for extra stuff. (Similarly, an emerging style on Unix is for setuid-root programs to spend most of their time *not* as root, but only to grab root during specific operations.) Even in a privilaged method, most of the operations tend to be unprivilaged.
An issue in how you do it, is the question of where the variable definitions happen. Right now, of course, there is no organized way to define global or pool variables in stock Squeak. I would hate for a dynamic link to become static, or vice versa, because some redefines a variable down the line. Thus being explicit in the syntax seems better as long as variable definitions are so easy to lose track of or to quietly get changed.
On the other hand, if there was a more structured way of managing large-scope variables, then using just a naming convention seems sensible.
Lex
One of the interesting possibilities that dynamic scoping might present is that it could be used to set a "behavioral context." Currently Squeak has only one global "behavioral context"...which is to say that all objects exhibit the same behavior (roughly speaking) under all runtime situations in which any given may find itself.
With some modification to the message binding algorithm and the structure of object memory, I think you could construct a system where objects could exhibit different behaviors when being utilized in different runtime "behavioral contexts."
Every object would have some behavior that it exhibits in the global (default) behavioral context. Additionally, you could add behavior (and corresponding state) that an object would only exhibit when it is being utilized from within a specific runtime behavioral context.
A real world analogy is that people, when in different contexts, often exhibit different behavior.
A good example is a domain object that exhibits GUI rendering behavior when utilized in the "Morphic" behavioral context. Upon first utilization in the Morphic context, the object would adopt the rendering behavior and state that you had previously defined for all objects of its class. This would eliminate the need to have two separate objects for the model and the view, yet still have all of the benefits of model-view separation. Furthermore, if you had different morphic applications for which different renderings were required for the same domain object, each application could define its own behavioral context (which would each inherit from the Morphic context). Method lookup would first look for methods in the specific behavioral context, and then follow an inheritance chain of behavioral contexts, with the global default behavioral context being the root of this heirarchy.
- Stephen
squeak-dev@lists.squeakfoundation.org