namgk / ambienttalk

Automatically exported from code.google.com/p/ambienttalk
0 stars 0 forks source link

Dynamic Protocols #16

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
When dynamically applying protocols, the use of callbacks can give rise to 
unexpected behaviour as 
the protocols are no longer active when the call-backs are activated. Moreover, 
since the 
dynamically activated protocol is not always lexically visible, the call-back 
cannot be held 
responsible to re-install them (it should be agnostic to their presence).

Hence we propose to introduce a call-back framework where language construct 
designers 
document when they use a call-back such that dynamic protocols can hook into 
this.

Original issue reported on code.google.com by tvcut...@gmail.com on 16 Sep 2008 at 1:47

GoogleCodeExporter commented 8 years ago
Pro: conceptually, we could see this as “the current actor mirror” being 
part of the context of a closure, so it 
should be ‘captured’ by closures as well.

Con: most closures have no need for capturing the ‘current actor protocol’ 
because they do not depend on it. 
Only a small minority depends on it (usually closures that serve as event 
handlers, identified by the when:* 
constructs)

Note: there is also a relationship with the dynamic variables library (see 
at/lang/dynvars.at), where the 
problem you mention can be seen more apparently. A dynvar can be assigned a 
value only within the context 
of a given block of code:

def x := dynVar: 0;
with: x is: 1 do: {
  // in this scope, x = 1
}

However, this simple abstraction fails to scale to our asynchronous execution 
model:

with: x is: 1 do: {
  // in this scope, x = 1
  when: someObj<-someMsg becomes: {
    // in the callback, x is no longer 1, as it is not executed in the dynamic extent of the original block closure 
application
  }
}

The current work-around in the dynvars library is the following: if the block 
of code passed to with:is:do: 
returns a future, then the re-setting of the dynvar to its previous value is 
postponed until that future has been 
resolved. The above piece of code will then work, since when:becomes: returns a 
future that is only resolved 
once its callback closure has been invoked. Within the context of the callback 
closure, x will be 1.

However, this still gives problems when there are multiple with:is:do: calls 
for the same dynvar in the 
program. The problem is this: with:is:do: relies on a synchronous 
call-stack-behaviour to set and re-set the 
dynvar. However, in an async event loop model, the synchronous call-stack 
behaviour does not necessarily 
hold. So, if calls return futures, these futures may be resolved "out of order" 
(i.e. not obeying the LIFO 
principle)

Original comment by tvcut...@gmail.com on 16 Sep 2008 at 1:55

GoogleCodeExporter commented 8 years ago
We could enforce that closures always capture the dynamic environment (i.e. the 
bindings of all dynamic 
variables at the moment of their creation) and reinstate them upon invocation. 
The current actor mirror can 
be seen conceptually as one such dynamic variable. 

This design decision does seem to introduce a new set of problems as well. 
First of all, we need to decide 
whether method definitions also capture the said dynamic environment or not. 
Second, a mechanism is 
needed to resolve conflicts, when a dynamic variable is given a value in a 
dynamic scope and a closure (or 
method) is invoked which associated a different value with the same variable. 
Using either value may result in 
counterintuitive results. 

None of these problems arise in the exemplar case, which uses when:* 
constructs. The underlying reason is 
that there, closures are used which will be invoked asynchronously, hence there 
is no surrounding scope 
which can introduce bindings for a dynamic variable (other than the default 
actor mirror).   

A conservative solution could therefore be to provide a callback: construct 
which takes a closure as argument, 
and wraps it to reinstate the bindings for all dynamic variables (including the 
actor mirror) upon invocation. 
Since the closure is wrapped, this mechanism does not interfere with regular 
application of the closure 
(required in case a non-literal closure is supplied). 

As a downside, using a callback: construct does require the implementors of 
when:* constructs to remember 
to use this idiom explicitly, and requires (minor) changes to all existing 
when:* construct implementations.

Original comment by smost...@gmail.com on 17 Sep 2008 at 8:13