[TransWarp] Explicit is better than implicit

Phillip J. Eby pje at telecommunity.com
Thu Apr 24 21:53:12 EDT 2003


Currently, in PEAK, when you do not specify a parent component for an 
object, it is considered a "root" object.  However, such "root" objects 
still acquire configuration from two special objects: the default 
AppConfig, and the SystemConfig.

Ty and I discussed today whether this is such a good thing.  For "short 
scripts" (and tutorials), it seems better to not have to explicitly specify 
parent components.  In this case, having an "implicit parent" of the 
default configuration objects makes it easy to write an application without 
needing any classes.

But, in a complex application, if one forgets to specify a parent component 
while creating an object, it is entirely possible to end up with an 
unintended "root", that does not follow the configuration of its usage 
context.  For example, the code below does that:

myApp = MyAppClass( thingItNeeds = SomeOtherClass() )

In the above example, 'myApp.thingItNeeds' will be a root object, even 
though the obvious intention is that it should be a child of 'myApp'.

This specific use case can be handled by changing the code of the default 
constructors to "suggest" a parent component to keyword arguments.  This is 
more implicit behavior, but as far as we can tell it is non-damaging: there 
are exceedingly few use cases for intentionally creating multiple root 
components within a single application.

Of course, adding this one implicit rule will not fix a host of other 
circumstances wherein one could unintentionally create another root 
component.  And we cannot reasonably add code to trap all of those 
circumstances.

So what we've discussed doing instead, is to make it so that unintended 
root components will "fail early and often", drawing one's attention to the 
error.  To do this, however, we must make certain things explicit that used 
to be implicit, and change some of the "rules of acquisition".

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'.  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 
'ob.setParentComponent(ROOT)'.)

If you perform a configuration lookup on a component hierarchy that does 
not "top out" to an explicitly designated root, you will receive an 
error.  This means that under most circumstances, an inadvertently created 
"root" object won't be very useful and you will detect it quickly.

Note also that explicitly designated "root" objects will *not* acquire 
configuration from outside of themselves.  If you create an explicit root, 
it will need to load base configuration data from 'peak.ini' (or wherever 
you prefer) in order to function.  We will probably provide some helper 
functions or mixins to make this easier to do, but 99% of apps shouldn't 
even worry about this.  Instead, they should set up their main application 
component as a child of the "default root" supplied by PEAK.

Indeed, now that we have finished the first version of the 
'peak.running.commands' framework, it should no longer be necessary for you 
to worry about this for most applications.  The preferred way to boot an 
application now is to create a class that implements 'ICmdLineAppFactory', 
and invoke it via the 'peak' startup script.  The startup process will 
automatically create an instance of your application as a child of an 
appropriate parent component.  (This will also be forward compatible with a 
hypothetical "PEAK Container" appserver that can run multiple applications 
simultaneously; each app would be started with a completely separate 
component hierarchy, having no way to interfere with each other.)

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)

storage.beginTransaction(root)
db('UPDATE parrot SET state="dead" WHERE breed="Norwegian Blue"')
storage.commitTransaction(root)

'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.

There are indeed many further details to how this will work, but I do not 
yet know what they are.  At this point, I'm looking for feedback, 
questions, etc. on this direction.  In particular, feedback on whether the 
explanation of the "new" model is clear, whether this is what you thought 
about how it works now, etc.  Thanks.




More information about the PEAK mailing list