[TransWarp] Explicit is better than implicit
Phillip J. Eby
pje at telecommunity.com
Sat Apr 26 18:22:36 EDT 2003
At 09:53 PM 4/24/03 -0400, Phillip J. Eby wrote:
>In some ways, these changes are simple. In effect, PEAK as a whole will
>be simpler when they are done, because they will allow us to trim certain
>extraneous concepts. For example, the notions of AppConfig and
>SystemConfig will both go away altogether, leaving behind only the concept
>of an "explicit root" and the "default root". All components will belong
>to a root, possibly the default one. All non-component objects (e.g.
>numbers, modules, and other non-PEAK objects) will belong to the default root.
>The default root component will be configured as PEAK's SystemConfig is
>now, from 'peak.ini'.
There's a minor revision needed to the above. After further discussion
with Ty, I realized we need to distinguish between the "default root" and
the "system root". (This distinction is only important in the context of a
PEAK application server; under typical circumstances they will be the same
By default, the "default root" will be the same object as the "system
root", but an application server will be able to replace the default root
with a special error-generating object, so that applications which rely on
using the default root will fail under circumstances where the default root
would be shared between applications. The "system root" will be the object
that all non-component objects will be considered children of, and the API
to get hold of the "system root" will be made obscure, since it nothing
should be accessing it directly.
The system root will be configured the way the current SystemConfig is
configured now, from 'peak.ini'.
> APIs that now implicitly use the default root when a component isn't
> specified will in future require it to be passed in some explicit form,
> even if it's only in the form of explicitly passing 'None' or a constant
> supplied by 'peak.api'. This will ensure that you always know where your
> properties or utilities are coming from, giving you an opportunity to
> think about whether that's the most sensible place for them to come from.
>All component hierarchies will be required to terminate with an "explicit
>root" object; that is, an object that has been explicitly designated as a
>root object, as opposed to an object whose parent simply has not been
>specified. (We don't know yet how this will happen; probably an explicit
>'ROOT' constant passed when you create the instance, or via
Some thoughts on how to spell this...
It should be very visible that you're making something a root object, so
that someone reading it knows you *intend* to make it a root object. This
means that simply using 'None' is probably not the best idea.
OTOH, lots of code now expects getParentComponent() to return None for a
root object. For example, getRootComponent() looks for a root component
So... my current thinking is that a root component should be required to
support an IConfigurationRoot interface. Declaring support for an
interface is a pretty strong statement that you know what making such a
component entails. And if you don't really know what you're doing, your
component will probably break early on you anyway.
It does seem to me that there is a slight risk here of creating a new FAQ:
"how come I can't look up this name? When I use it in a binding.bindTo()
it's fine, but if I use 'naming.lookup()' it gives me this error." We'll
have to make the error message for a missing configuration root suggest how
to correct the problem.
>In essence, creating a "root" object is a bootstrapping job; application
>code (i.e. code in classes and modules) shouldn't care. Short scripts
>will need to explicitly use the default root, e.g.:
>from peak.api import *
>root = config.defaultRoot()
>db = naming.lookup("someDB://foo:bar@baz/spam", root)
>db('UPDATE parrot SET state="dead" WHERE breed="Norwegian Blue"')
>'config.defaultRoot()' is a proposed spelling; the actual name has not
>been decided. It may be that passing a special 'DEFAULT_ROOT' object to
>the API's will tell *them* to look up the default root. This isn't clear yet.
Currently I'm leaning towards requiring you to retrieve the default root
and use it explicitly. I think this will be less ugly to use than passing
a singleton, and will require less internal change to PEAK. (I'll need
only to get rid of the default 'None' value for the appropriate API
There is one other terminology/API spelling issue. Should we speak of
"roots" or "applications" as the primary terminology?
Root is technically correct, but application is more relevant to intended
use. The code example above seems more natural to me when written this way:
from peak.api import *
app = config.getDefaultApp()
db = naming.lookup("someDB://foo:bar@baz/spam", app)
db('UPDATE parrot SET state="dead" WHERE breed="Norwegian Blue"')
However things then begin to veer into strange territory when it becomes
necessary to discuss the "system app". OTOH, if you always make your
objects children of the default app, then until we have some kind of
multi-application server, this issue is irrelevant.
The other thing I like about it is that it makes sense to say that "to use
the configuration system, a component must be part of an application. That
is, it must have an application as its root component." Then, we can say
that "an application is a root component that implements
config.IApplication", and that the easiest way to get a useful application
is to use 'config.getDefaultApp()'.
Multi-threading is an interesting issue here. It's rather tempting to make
'getDefaultApp()' return a different app according to what thread you're
in. In practice, I think that running multi-threaded applications with
PEAK is too open-ended of a question right now to know what the right thing
to do is.
So what of the "system" application? Is there a better name for it? The
only reason we need it is to support naming lookups in the context of
classes. And there we only need it if a URL is used in a
'referencedType'... Maybe we could get rid of the "system" application
altogether! I'm not currently aware of any utility or property lookups
that are done in the context of a class or module object, and it's not
generally useful to do it.
Right now the only thing we do with "foreign" components like modules and
classes is relative name lookups. Those are safe to use in any component
tree, and don't depend on configuration lookups. Granted, if the relative
name lookup fails, it will probably cascade to an attempt to use the
default naming context, which will fail because there are no properties
available to specify the context. But that could be handled by having the
lookupComponent function not fall back in that case, and just throw a
NamingError without bothering to try the default naming context (since
there won't be one).
Okay... so if we don't need a "system" application, do we need a "default"
application either? Hmmm... maybe not. If we can create an app at the
drop of a hat with 'config.App()', what difference is that in practical
terms? The only thing saved is multiple loads of 'peak.ini'. And, if
you're creating a sensible application, *only* a __main__ module or script
(or a test suite) should be creating app objects.
Wow. Explicit really *is* better than implicit. This is the exact same
thing I was just saying on ZODB-Dev about the 'get_transaction()' machinery
in ZODB. In any kind of reasonable component architecture, singletons
suck. They don't reduce complexity, they add to it. PEAK has been
laboring under a lot of extra complexity to support the "special"
Interestingly, once the need for the singletons goes away, so does the need
for a special declaration of "root". We're back to where it's okay for any
object to be a root; it just won't be usable for naming system lookups, or
anything else that relies on a peak.ini-supplied default. So if you
accidentally make a root, it won't fail silently.
But, it might be confusing to simply "not find" properties or utilities, or
to get an error message that suggests something is wrong with the naming
system. So I still think that the IConfigurationRoot interface is a good
idea. If a configuration lookup "falls off the top", and the top doesn't
support IConfigurationRoot, we could issue a warning or an error specific
to that. Perhaps, if it's something that would result in a non-error, like
a property lookup with a default, we should issue a warning, and for a
property lookup without a default we would go ahead and raise it as an
error (since the lookup would fail anyway).
Nah. Probably we should just issue an error in all cases; it'd be better
to fix a problem like that early rather than leave it dormant.
Hmm. That was a lot of rambling in this message. Guess I'll write another
one summarizing the actual decisions from this one, leaving this as a
record of the rationale.
More information about the PEAK