[PEAK] What do you use 'offerAs' for?

Tiago Cogumbreiro cogumbreiro at gmail.com
Tue Sep 20 07:29:23 EDT 2005


Hi Philip,

The last usage I did with "offerAs" was while creating a GUI
application, the idea was to create the graphical interface with a
bunch of subcomponents (one for the toolbar and menus, the other for a
treeview and populating it and so on) , the "offerAs" as used to
expose PropertyName("myapp.Window") or PropertyName("myapp.Toolbar")
which is an obvious improvement in GUI code since you can access any
exposed widget by simply obtaining it.

One thing I didn't understand was how do you define and use your
"dynamic variables"?

On 9/20/05, Phillip J. Eby <pje at telecommunity.com> wrote:
> No, don't worry, I'm not planning to remove it.  :)  At least, not any time
> soon.
> 
> Here's the thing.  I've been mulling over one of the big aspects of what
> PEAK does - providing context for components - and realizing that it looks
> a lot like a giant kludge to work around the absence of Lisp-style dynamic
> variables.  If this is true, it would mean that PEAK's API could be
> dramatically simplified for certain types of tasks, to the point of seeming
> to disappear altogether.  In essence, many aspects of PEAK would look a lot
> more like a library and a lot less like a framework.
> 
> So, the thing that we found a couple of years back was that most PEAK
> applications tend to concentrate their configuration at some "global"
> level.  We dubbed this a "service area", because you tend to have a bunch
> of singleton services in it, that are shared by virtually every component
> that needs a service of the given type.  An application can have more than
> one service area, but this is usually for special reasons, like the
> supervisor tool that maintains a pre-populated service area for processes
> that it's going to fork off from itself, and a separate service area for
> managing the child processes.  Similarly, an application server hosting
> multiple applications would want separate service areas.
> 
> So, our implementation shifted a wee bit to make it easier to do this sort
> of thing.  We added an explicit ServiceArea type and interface, facilities
> to automatically create singletons when you ask for a general-purpose
> component, and so on.
> 
> There are some gotchas with the existing setup, however.  One use case for
> creating an alternate service area is when you want to say, create a second
> transaction independent of your current transaction.  For example, if
> you're in an error handler rolling back a primary transaction, you might
> need a separate transaction in which to send out an email notice about the
> error.  This is rather awkward in PEAK at the moment because you can't just
> create a separate transaction and share the remaining components in your
> existing service area, because the coupling between components is via the
> service area.  In other words, all your existing components that use a
> transaction will be tied to your original service area.  This is a bit of a
> pain, to say the least.
> 
> Similarly -- and this is the use case that got me thinking about all this
> -- peak.web right now jumps through a lot of hoops with its Context object
> to keep track of a whole bunch of theoretically-orthogonal but practically
> linked variables.  The base URL, current user, current skin, and a bunch of
> other stuff.  Not only does this induce a bunch of slow context lookups to
> find common values, it has no clean way of being extended by multiple
> components.  You can't just go and add a 'shopping_cart' request variable
> cleanly, for example.  Also, the way the peak.web InteractionPolicy works,
> it's hard to replace any of the things it configures at lower levels of
> your object hierarchy.  As a result, peak.web isn't really geared to host
> multiple applications in the same site very well.
> 
> What I realized at some point was that peak.web's Context variables really
> want to be like Lisp dynamic variables, that can be set in a function, and
> retrieved by any code called from that function.  Whenever you return from
> a function that sets such a variable, its old value would be
> restored.  Thus, you could for example track the current user by creating a
> web.user variable, something like:
> 
>      import context
>      user = context.Variable()
> 
> You could then in a publishing routine do something like:
> 
>      web.user.push(auth_svc.getUser(request_data))
>      try:
>          print "the current user is", web.user.get()
>      finally:
>          web.user.pop()
> 
> Or in Python 2.5:
> 
>      with web.user(auth_svc.getUser(request_data)):
>          print "the current user is", web.user.get()
> 
> Looks simple enough, yes?  Under the hood, Variable objects would be
> thread-local.  And, to support peak.events and Twisted, there would be a
> way to take a snapshot of all the current context variables, and restore
> the snapshot later, so that e.g. events.Task objects could swap their
> current context in and out.
> 
> I've actually prototyped data structures that do all this, and they are
> amazingly efficient - much more so than multi-level parent component
> walking to find things.  So I got to thinking about other uses.
> 
> One thing that occurred to me fairly quickly is that a *lot* of the edge
> cases in the usefulness of PEAK's component model can be fixed by using
> dynamic variables in place of hierarchy-based lookups.  For example, the
> idea of the "current transaction" is really time-bound, not
> space-bound.  It really should be "the transaction I'm in right now", not
> "the transaction service for my service area".  The latter just happens to
> be a useful approximation to the former, but if you can actually time-bind
> instead of space-bind, then why bother with the approximation?
> 
> So I tried writing some code samples using the idea of making
> 'storage.transaction' a dynamic variable, and I quickly found that
> 'transaction.get().begin()' was a pain.  So, I came up with the idea of a
> 'context.Service', which would just be a proxy that delegated all its
> attributes and methods to the get() of a specified Variable.  I haven't
> worked out the precise API for defining and initially configuring one, but
> it would allow you to do stuff like 'storage.transaction.begin()' and
> 'storage.transaction.commit()', and automatically forward the method calls
> to the value of the hidden Variable, so that they go to the right object
> for the current context.
> 
> Now, here's something interesting.  If you look closely, you'll see that I
> just wiped out the need to Obtain(ITransaction).  In fact, I wiped out the
> need for ITransaction itself, except for documentation or adaptation.  I
> just say, "storage.transaction", and there it is.  How simple can you get?
> 
> Really, the only way to get simpler is with a global variable.  But global
> variables are subject to unrestricted manipulation, and they don't play
> well with threads or events.Task pseudothreads.   But all context.Variables
> can be swapped out with just one call:
> 
>      old_vars = context.swap(new_vars)
> 
> This operation takes around 2 microseconds on my PC, so it doesn't slow
> down inter-Task switching.  And, each thread has its own current "state of
> all variables" mapping, so there's no inter-thread pollution.  Finally, the
> API is designed so that push() and pop() have to be paired, so manipulation
> is constrained to relatively-comprehensible hierarchies based on calling
> contexts.
> 
> All this sounds really great in theory.  But how often would you need to
> change context variables?  If it's done a lot, it would probably be a pain
> to push and pop all those variables.  So, I decided to grep the PEAK source
> for uses of 'offerAs', because 'offerAs' is a way of saying, "I'd like to
> set a new value for this configuration key in the context beneath me."
> 
> What I discovered shocked me.  Of the 38 uses of offerAs in the PEAK source
> tree, *18* were in tests, mostly to override a default service with a mock
> one for the test.  What's more, virtually all of the remaining uses were
> ones that would be unequivocally easier to use, understand, and extend if
> they used dynamic variables instead.  Some were transaction-related, but
> there was another edge case I hadn't thought of before the grep: commands.
> 
> You see, command objects use component context to get their stdin, stdout,
> argv, envrion, etc.  As soon as I saw that in the grep, I recalled all of
> the pain I went through getting that part of the running.commands framework
> to work correctly, and instantly saw that it would have been a piece of
> cake with dynamic variables, because they really "want" to be
> calling-context based.  It's only because the "real" (Python-supplied)
> versions of those variables aren't dynamic (thread/task-local) that any of
> the fancy footwork was needed in the first place!
> 
> Another new use case was the current IEventLoop or IMainLoop a task is
> running under.  There are aspects of that one that have bothered me for
> years!  And yet, the dynamic variable version of those is so simple and
> elegant and clean it makes me amazed I never thought of it before.  My
> prototype implementation doesn't use any Python features that weren't
> around in 2.2, so in principle I could have done this years ago.  (Granted,
> the 'with' statement coming in Python 2.5 provided some inspiration for the
> idea, and it will really be the easiest way to use dynamic variables, all
> things considered.)
> 
> So, it's gotten me quite curious, because apart from testing, *every*
> nontrivial use of offerAs in the PEAK core was a part of some sort of cruft
> that would've been incredibly better with dynamic variables.
> 
> So what uses do *you* have for offerAs?  Would they be clearer or less
> clear by using dynamic variables?  I'm intrigued by the possibility of
> simplifying the PEAK core quite a bit with this notion.  Most
> binding.Obtain() bindings would be replaced by simple direct reference to
> services, although component-local Obtain's would still be useful.  All the
> crufty bits of PEAK I found would become non-crufty.  All of the colorful
> configmaps and eigenvalues and all that would mostly melt away, with the
> .ini files just being used to set dynamic variables instead.
> 
> There are a lot of things that would have to be worked out to do that, of
> course.  Especially since the API, although improved, would likely be quite
> different.  I haven't even worked out the destination, let alone how the
> existing codebase would transition.  And the "when" of all this is quite
> questionable.  I have no idea when I'd even really start.
> 
> But in the meantime, I am quite curious.  If you have any uses for offerAs,
> please post about them.  I'm interested in whether there are any uses that
> really are better than using a dynamic variable for the same thing.
> 
> _______________________________________________
> PEAK mailing list
> PEAK at eby-sarna.com
> http://www.eby-sarna.com/mailman/listinfo/peak
> 


-- 
Tiago Cogumbreiro <cogumbreiro at users.sf.net>

http://s1x.homelinux.net/



More information about the PEAK mailing list