[Zope3-dev] Interface declaration API

Jim Fulton jim@zope.com
Tue, 11 Mar 2003 16:35:02 -0500


Phillip J. Eby wrote:
> At 02:46 PM 3/11/03 -0500, Jim Fulton wrote:
> 
>> Subclassing is done to reuse implementation. If interfaces were
>> inherited, then you would be unable to subclass a class without
>> also promising to implement the base class interfaces.  I've always
>> viewed this as an undesireable requirement, but maybe I'm wrong,
>> since so many people desire it, ;) So I'm willing to give on this.
> 
> 
> Ironically, you then go on to say that 'doesNotImplement()' would be a 
> YAGNI.  :)  I actually suggested 'doesNotImplement()' as a way to deal 
> with the situation above.  If one wishes to reject base class 
> interfaces, one might then do so explicitly.

The way you would reject base-class interfaces is to use implements. That is,
if you use implements, you implement *just* what you say you implement,
so I still think 'doesNotImplement()' is a yagni.

> 
> 
>> Soooooooo ....
>>
>> Let's suppose we decided that in:
>>
>>   class C(A, B):
>>
>>      alsoImplements(IFoo)
>>
>> The outcome would be that C's instances would implement
>> IFoo and whatever A's and B's instances implement.
>>
>> This is implementable, but with a hack. The hack is that the
>> descriptor needs to update itself the first time that it's used. :(
>>
>> In::
>>
>>
>>   class C(A, B):
>>
>>      pass
>>
>> The outcome would be that C's instances would implement
>> whatever A's and B's instances implement.
>>
>> This too requires a pretty heavy hack.  The first time you tried to
>> get the instance interface spec for a C, you'd inherit a spec from
>> A or B. The inherited descriptor would detect that it was inherited
>> and create a new descriptor for C, merging the interfaces from A and
>> B.
>>
>> Finally, with:
>>
>>   class C(A, B):
>>
>>      implements(IFoo)
>>
>> The outcome would be that C's instances would implement
>> only IFoo.
> 
> 
> I've previously implemented late-binding class descriptors of this kind, 
> and I agree that they aren't appropriate for Zope 3.  One potential 
> issue, for example, is thread-safety of the object that does the late 
> binding (because non-persistent classes are normally shared between 
> threads).
> 
> I do notice, however, that your description seems to imply a 
> descriptor-based implementation. 

That's what I was thinking of. Hm. I'll have to ponder a non-descriptor
implementation. It might make things cleaner if we decide change the inheritence
semantics.  I was thinking in terms of descriptors because they avoid having
to sniff to decide if something is a class, which is *really really* painful
in the presense of security proxies.  I guess we shouldn't get too hung up
on implementation difficulties at this point and focus on what we want.

 > If the intention is to encapsulate the
> implementation, it's not clear to me that putting the behavior in a  
> descriptor is the most useful way to go.  For example, 'implementedBy()' 
> might look something like this:
> 
> def implementedBy(klass):
> 
>     spec = klass.__dict__.get('__implements__')
> 
>     if spec is None:
>         spec = klass.__implements__ = InterfaceSpecification(
>             *tuple([implementedBy(c) for c in klass.__bases__])
>         )
> 
>     return spec

where this is a non-descriptor-based approach.

> Notice this also has the same thread-safety issue.

Yes.

 > Also, you have to
> have *something* that does this, if you want 'implementedBy()' to return 
> a mutable object!

Is your point that you have to mutate the class if you want 'implementedBy()'
to return a mutable object? You are right, although you could arrange
for the mutation to be defered until you actually modified the spec, which
wouldn't be so bad.

> So, if you really want to simplify this, I'd suggest going back to 
> immutable interface specs. 

I don't see how that simplified anything. Is your point that you want to
avoid having a query method modify the thing being queried?


 > This would then require an explicit
> rebinding (e.g. klass.__implements__ += IFoo)

That would be unacceptable, as it would expose the declaration implementation,
which we want to avoid.

 > and wouldn't need a check
> for whether the specs were inherited or not.  You also wouldn't need a 
> descriptor, which means that "classic" classes might be better 
> supported.  (I'm not positive that any of classic classes' limitations 
> on descriptors are an issue here, though.)

Is your objection that:

   getImplemented(cls)

could mutate the class?

What if it didn't, but:

   getImplemented(cls).set(I)

did?



> In order to preserve encapsulation, you would need to have functions 
> like  'addImplements()', 'addProvides()', 'setImplements()', etc.  While 
> this expands the number of API functions, it also increases the 
> explicitness of such operations (one may grep for 'setImplements()' more 
> easily than for 'foo=implementedBy(Bar) ...  multiple lines of code ... 
> foo.add(IBaz)'.  That is, one is now required to put the operation in 
> the same place with the thing being operated upon.
> 
> Doing this would actually give you an implementation benefit to sticking 
> with "first in MRO" inheritance semantics for interface declarations, 
> er, specifications.

how so?

> (By the way...  I always stumble on calling them "interface 
> specifications".  To me, the interface specification is the "class 
> IFoo(Interface):" block, while the "__implements__ = IFoo" is an 
> *interface declaration*.  But I may be alone in this.)

I see your point.

> Anyway...  what I'm saying is, if you stick with mutable specifications, 
> you might as well implement "merged __bases__" inheritance, because 
> there is no less implementation complexity. 

Because you have to mutate classes in either case?

 > AFAICT, to reduce the
> implementation complexity from what you described, you have to also make 
> specifications immutable, so that it never matters if the specification 
> is inherited or not.  Otherwise, you are right back in the same boat of 
> needing to either have a lazily-binding descriptor, or a function that 
> acts like one "from the outside".


Even if they were immutable, you'd want to manipulate the descriptor and/or the class
for efficiency sake if you don't want the current inheritence semantics.

Jim

-- 
Jim Fulton           mailto:jim@zope.com       Python Powered!
CTO                  (888) 344-4332            http://www.python.org
Zope Corporation     http://www.zope.com       http://www.zope.org