[PEAK] PEAK Components: beginner questions

Duncan McGreggor python at adytumsolutions.com
Tue Sep 21 22:56:22 EDT 2004


On Sep 21, 2004, at 12:23 PM, Phillip J. Eby wrote:

>> from protocols import Interface, advise
>> from peak.binding.components import Component, Obtain, Make
>
> Don't directly import from modules inside PEAK packages.  If you 
> really don't want to use 'binding.Component', et al, do this:
>
>     from peak.binding.api import Component, Obtain, Make
>
> because direct imports are not guaranteed to be stable across 
> releases.  I might completely reorganize the internal modules of the 
> 'binding' package, and your code would break.  That's what the 'api' 
> modules are for.

Man, I've been wondering about that :-) Very cool... I'm going to look 
at that code again to see how you implement that. We're releasing some 
software that's very beta, and the api is young and in flux... doing 
something like this will put everyone at ease.

>> class IPhysiology(IBody):
>>     '''Physiology interface'''
>>     def getHeartRate():
>>         '''provide information about the heart rate'''

>> class Physiology(Component):
>>     advise(instancesProvide=[IPhysiology])
>>     body = Make(Body, offerAs=[IBody])
>>     def getHeartRate(self):
>>         return "nice rhythm!"
>
> As written, your Physiology class does not correctly implement 
> IPhysiology, because it doesn't offer 'getFuelLevel' or 
> 'getDamageLevel' methods.  If you want to delegate these methods to 
> 'body', you have to do something like :
>
>      getFuelLevel = getDamageLevel = binding.Delegate('body')
>
> which will at runtime supply those methods as cached references to 
> those methods from the 'body' attribute.

Yeah, that's exactly the kind of thing I was looking for.

> Meanwhile, your reasoning for making the 'body' binding 
> 'offerAs=[IBody]' is unclear.

No reasoning involved... I was shooting in the dark, based on the 
component hierarchy example in the wiki.

> I'm guessing that you are confusing interface declarations (e.g. 
> 'protocols.advise()') with configuration (e.g. 'config.lookup()').

I'm guessing you're right ;-) I still don't know enough about PEAK 
internals to confirm this, though.

> 'offerAs' is a mechanism to interface with the PEAK configuration 
> facility, which is used by 'binding.Obtain' to gain access to 
> components.  'offerAs' has nothing to do with the actual interfaces a 
> component does or does not implement.  'offerAs' simply registers the 
> attribute under  "configuration keys" (such as interfaces; see 
> config.IConfigKey) to allow ohter components to find those objects 
> when needed.

Ah, that makes sense now...

>> class IMind(Interface):
>>     '''Mind interface'''
>>     def perceive():
>>         '''provide perception'''
>>     def think():
>>         '''provide 'thought' mechanism'''
>>
>> class Intellect(Component):
>>     advise(instancesProvide=[IMind])
>>     def think(self):
>>         print "hmmm, fire bad, thinking hard..."
>
> This component does not provide 'IMind', as it only offers 'think()'.
>
>> class Vision(Component):

>> class Hearing(Component):
> These two component are similarly broken as written, only they're each 
> missing 'think()'.

Yeah... I see now this is sloppy. I'll have to rework those. Probably 
think doesn't belong in IMind, just percieve. Intellect.percieve might 
be a summing utility of other "minds" (in this case Vision and 
Hearing), and then Intellect could have it's own method "think."

>> class IEmotion(Interface):
>>     '''Emotion interface'''
>>     def getIntensity():
>>         '''provide aa means for obtaining emotional intensities'''
>>
>> class Emotion(Component):
>>     advise(instancesProvide=[IEmotion])
>>     def __init__(self):
>>         self.feeling = 0
>>     vision = Make(Vision, offerAs=[IMind])
>>     hearing = Make(Hearing, offerAs=[IMind])
>>     physiology = Make(Physiology, offerAs=[IPhysiology])
>
> Once again, some confusion between configuration keys (offerAs) and 
> implementation (protocols.advise).  It makes no sense at all to offer 
> two attributes under the same configuration key, as only one will end 
> up being used for lookups, and I'm not even sure which one.  However, 
> for what this class does, and what it claims to implement, you could 
> drop all the 'offerAs' clauses and it would have no effect, because 
> you're not ever 'Obtain'-ing or otherwise looking up any of these 
> configuration keys.
>
>> class Movement(Component):
>>     advise(instancesProvide=[IMind])
>
> What the heck?  Why does Movement provide IMind?
> It certainly doesn't actually implement IMind, so I'm guessing that 
> was a typo for IMovement.

*chuckles*
man, sorry about that -- yeah, it's a typo.

>> * What's the best way for an interface to inherit from another 
>> interface? (I know this has been discussed... I just can't find it)
>
> That depends on what you mean by "inherit".  I'm sure I don't know 
> what you mean, since you have lots of interface inheritance in your 
> example.

Specifically:

class IBody(Interface):
...

class IPhysiology(IBody):
...

Given that the interfaces make sense and it is genuinely appropriate 
for IPhysiology to inherit from IBody, is this how it should be done? 
Or is there a PEAK way of doing it?

Usage clarification: IPhysiology would only be used in software agents 
simulating living entities; with them, there would be no implementation 
of IBody. However, a manufacturing robot (e.g., one you would find in 
an assembly line) simulation would implement IBody (and obviously not 
IPhysiology).

>> * I used Make for adding components to a class; is that the right 
>> approach?
>
> Make is for explicit composition of new component instances.  If 
> that's what you intended, then yes that's the right approach.
>
>> * When would I choose Make over Obtain? and vice versa?
>
> Obtain can access the "binding" system, the "configuration" system, or 
> the "naming" system, depending on what argument you give.  As a 
> general rule, accessing the binding or configuration systems will 
> cause Obtain to provide existing components, and accessing the naming 
> system will produce a new component.  But there are exceptions, 
> especially since you can create your own 'binding.IComponentKey' 
> implementations for use with 'Obtain', and they can do whatever you 
> want.
>
> But the general idea of Obtain is, "I know this is out there 
> somewhere, go get it for me".
>
> Make, on the other hand, generally means, "make me one of these".  But 
> again, you can create your own 'binding.IRecipe' implementations that 
> do whatever you want, so there are exceptions to that rule as well.  
> You can also apply 'Make' to any function or lambda, so the 
> possibilities of what it can do are endless.  In a way, 'Obtain' is a 
> specialization of 'Make' that just looks things up instead of making 
> them.

This is just fantastic; I've pasted it into the wiki (as well as some 
other component-related info from the mail list).

>> * Right now, a physiology instance p has to use p.body to get to the 
>> Body methods; is there are better/more direct way to do this? Seems a 
>> little awkward... (since I'm used to object inheritance)
>
> You can use inheritance, if you like.  But if you want to delegate, 
> use:
>
>     getFuelLevel = getDamageLevel = binding.Delegate('body')
>
> as I mentioned earlier.  This is roughly equivalent to:
>
>     getFuelLevel = binding.Obtain('body/getFuelLevel')
>     getDamageLevel = binding.Obtain('body/getDamageLevel')
>
> only more succinct.

What are some of the factors one should consider when deciding on to 
use inheritance in a case like this, or delegation? I.e., when 
might/should one choose one over the other?


>> * The Russian Doll component code of Emotion and Action may not be 
>> appropriate for an agent model, but I wanted to experiment with 
>> component "depth"... my approach seems amateurish, since I don't know 
>> what I am doing. Better approaches?
>
> I don't know why you've factored the interfaces the way you have, so 
> if I were doing it, I'd lump the whole darn thing into a single 
> component.  You haven't shown motivation for needing to *replace* 
> certain components with others, like putting a bigger power supply 
> into a microwave oven to support a bigger cooking compartment.

You're right; I haven't shown motivation for this approach. There are 
many accepted methodologies (equations, combinations of equations) for 
simulating various agent processes. We've been focusing on a couple 
different emotional models right now, so that's what we'll be 
componentizing first; however, all agent classes can be replaced with 
different implementations that represent different theories or 
different models.

In fact, we won't have an implementation called "Emotion" ... probably 
something more like emotion.StandardOCCModel, 
emotion.SilvermanMarkovOCCModel, etc., with one of them being used in a 
software agent at any given time... and I guess the possibility exists 
for the combination of multiple models. That might have to be done at 
the class level, as opposed to achieving it through use of 
components... need to give that more thought.

That being said:

1) your microwave example really hit home -- thanks ;-) That's exactly 
the kind of example I needed to get me into a better understanding of 
the abstractions in PEAK. Much better than an example that has to deal 
with the complexities in a software agent.

2) Re-readings of this have inspired reconsideration of the original 
approach, and I am thinking of ways to minimize the number of 
components, despite my original resistance to that approach. The more 
thought I give your words, the wiser such an approach seems. Thanks!

> The possibility of replacement is one of the biggest motivating 
> factors in determining how responsibilities are separated into 
> components.
>
> Placement is determined by who needs the component; what is it's 
> scope?  The buttons on a microwave oven are used only within the 
> "control panel" component, but the "power supply" is used throughout.  
> Therefore, "power supply" is a child of microwave oven.  "Control 
> Panel" would 'Make' buttons, but 'Obtain' a power supply from its 
> parent component, the oven.

Yes, thanks! This is what I was trying to do while stumbling around in 
the dark.

> So, place components at the scope of their use, "offering" them to any 
> child components that require their services, e.g.:
>
>
>     class MicrowaveOvenModel263(binding.Component):
>
>         powerSupply = binding.Make(MediumPowerSupply, 
> offerAs=[IPowerSupply])
>
>         controlPanel = binding.Make("cpanels.ControlPanelType2", 
> offerAs=[IControlPanel])
>
>         radiativeElement = binding.Make(Radartronic263, 
> offerAs=[IRadiative])
>
>
>     class ControlPanelType2(binding.Component):
>
>         powerSupply = binding.Obtain(IPowerSupply)
>
>         button0 = binding.Make(Button)
>
>         # ...

I have also added this to the wiki -- pure gold :-) This single snippet 
of code has answered so many questions for me and has catalyzed half a 
day of additional digging into PEAK.

>> * In the Action class, Emotion is offered as IMind and IPhysiology, 
>> due to the fact that Vision and Hearing (in Emotion) provide IMind 
>> interfaces. Is this the right way to do something like this?
>
> I don't think I can answer your question, because I think it's based 
> on a misunderstanding of what offering things is *for*, as well as 
> lacking an explanation of what you *want* to accomplish.  I hope, 
> therefore, that my example above will answer whatever question you 
> should have asked instead.  :)

*laughs*
Yes. Indeed, it did.

In fact, I have re-read this email multiple times throughout the day, 
and each time I glean some additional insight into not only PEAK, but 
design decisions as well. Very much appreciated -- thank you!

Duncan




More information about the PEAK mailing list