On Mon, Aug 21, 2017 at 7:12 AM, Nick Coghlan <ncogh...@gmail.com> wrote:

> On 21 August 2017 at 15:03, Guido van Rossum <gu...@python.org> wrote:
> > Honestly I'm not sure we need the distinction between LC and EC. If you
> read
> > carefully some of the given example code seems to confuse them. If we
> could
> > get away with only a single framework-facing concept, I would be happy
> > calling it ExecutionContext.
>
> Unfortunately, I don't think we can, and that's why I tried to reframe
> the discussion in terms of "Where ContextKey.set() writes to" and
> "Where ContextKey.get() looks things up".
>
> Consider the following toy generator:
>
>     def tracking_gen():
>         start_tracking_iterations()
>         while True:
>             tally_iteration()
>             yield
>
>     task_id = ContextKey("task_id")
>     iter_counter = ContextKey("iter_counter")
>
>     def start_tracking_iterations():
>         iter_counter.set(collection.Counter())
>
>     def tally_iteration():
>         current_task = task_id.get() # Set elsewhere
>         iter_counter.get()[current_task] += 1
>
> Now, this isn't a very *sensible* generator (since it could just use a
> regular object instance for tracking instead of a context variable),
> but nevertheless, it's one that we would expect to work, and it's one
> that we would expect to exhibit the following properties:
>
> 1. When tally_iteration() calls task_id.get(), we expect that to be
> resolved in the context calling next() on the instance, *not* the
> context where the generator was first created
> 2. When tally_iteration() calls iter_counter.get(), we expect that to
> be resolved in the same context where start_tracking_iterations()
> called iter_counter.set()
>
> This has consequences for the design in the PEP:
>
> * what we want to capture at generator creation time is the context
> where writes will happen, and we also want that to be the innermost
> context used for lookups
> * other than that innermost context, we want everything else to be dynamic
> * this means that "mutable context saved on the generator" and "entire
> dynamic context visible when the generator runs" aren't the same thing
>
> And hence the introduction of the LocalContext/LogicalContext
> terminology for the former, and the ExecutionContext terminology for
> the latter.
>

OK, this is a sensible explanation. I think the PEP would benefit from
including some version of it early on (though perhaps shortened a bit).


> It's also where the analogy with ChainMap came from (although I don't
> think this has made it into the PEP itself):
>
> * LogicalContext is the equivalent of the individual mappings
> * ExecutionContext is the equivalent of ChainMap
> * ContextKey.get() replaces ChainMap.__getitem__
> * ContextKey.set(value) replaces ChainMap.__setitem__
> * ContextKey.set(None) replaces ChainMap.__delitem__
>
> While the context is defined conceptually as a nested chain of
> key:value mappings, we avoid using the mapping syntax because of the
> way the values can shift dynamically out from under you based on who
> called you - while the ChainMap analogy is hopefully helpful to
> understanding, we don't want people taking it too literally or things
> will become more confusing rather than less.
>

Agreed. However now I am confused as to how the HAMT fits in. Yury says
somewhere that the HAMT will be used for the EC and then cloning the EC is
just returning a pointer to the same EC. But even if I interpret that as
making a new EC containing a pointer to the same underlying HAMT, I don't
see how that will preserve the semantics that different logical threads,
running interleaved (like different generators being pumped alternatingly),
will see updates to LCs that are lower on the stack of LCs in the EC. (I
see this with the stack-of-dicts version, but not with the immutable HAMT
inplementation.)


> Despite that risk, taking the analogy further is where the
> DynamicWriteContext + DynamicLookupContext terminology idea came from:
>
> * like ChainMap.new_child(), adjusting the DynamicWriteContext changes
> what ck.set() affects, and also sets the innermost context for
> ck.get()
> * like using a different ChainMap, adjusting the DynamicLookupContext
> changes what ck.get() can see (unlike ChainMap, it also isolates
> ck.set() by default)
>

Here I'm lost again. In the PEP's pseudo code, your first bullet seems to
be the operation "push a new LC on the stack of the current EC". Does the
second bullet just mean "switch to a different EC"?


> I'll also note that the first iteration of the PEP didn't really make
> this distinction, and it caused a problem that Nathaniel pointed out:
> generators would "snapshot" their entire dynamic context when first
> created, and then never adjust it for external changes between
> iterations. This meant that if you adjusted something like the decimal
> context outside the generator after creating it, it would ignore those
> changes - instead of having the problem of changes inside the
> generator leaking out, we instead had the problem of changes outside
> the generator *not* making their way in, even if you wanted them to.
>

OK, this really needs to be made very clear early in the PEP. Maybe this
final sentence provides the key requirement: changes outside the generator
should make it into the generator when next() is invoked, unless the
generator itself has made an override; but changes inside the generator
should not leak out through next().


> Due to that heritage, fixing some of the examples could easily have
> been missed in the v2 rewrite that introduced the distinction between
> the two kinds of context.
>

At this point I would like to suggest that maybe you and/or Nathaniel could
volunteer as co-authors for the PEP. You could then also help Yury clean up
his grammar (e.g. adding "the" in various places) and improve the general
structure of the PEP.


> > (Another critique of the proposal I have is that it adds too many
> > similarly-named functions to sys. But this email is already too long and
> I
> > need to go to bed.)
>
> If it helps any, one of the ideas that has come up is to put all of
> the proposed context manipulation APIs in contextlib rather than in
> sys, and I think that's a reasonable idea (I don't think any of us
> actually like the notion of adding that many new subsystem specific
> APIs directly to sys).
>

I don't think it belongs in contextlib. That module is about contexts for
use in with-statements; here we are not particularly concerned with those
but with manipulating state that is associated with a logical thread. I
think it makes more sense to add a new module for this purpose. I also
think that some of the framework-facing APIs should probably be methods
rather than functions.

-- 
--Guido van Rossum (python.org/~guido)
_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to