[Python-Dev] type categories

Alex Martelli aleax@aleax.it
Sat, 24 Aug 2002 23:33:38 +0200

On Saturday 24 August 2002 07:06 pm, Oren Tirosh wrote:
> On Sat, Aug 24, 2002 at 05:37:36PM +0200, Alex Martelli wrote:
> > On Saturday 24 August 2002 05:15 pm, Jeremy Hylton wrote:
> > > Good point, Oren.  We now have two requirements for interfaces that
> > > are different than the standard inheritance mechanism.  It should be
> > > possible to:
> > >
> > >   - inherit from a class without implementing that class's interfaces
> > >
> > >   - declare that a class implements an interface outside the class
> > >     statement
> > >
> > > It's harder to support the second requirement using the current
> > > inheritance mechanism.
> >
> > The second requirement is a good part of what adaptation is meant
> > to do.
> I am not talking about situations where the object does not meet your
> expectations and needs to be adapted - I'm talking about situations where
> it actually does and the only problem is how to describe that fact
> properly.

Adaptation IS one way to "describe that fact properly", given that checks
are anyway constrained to happen at runtime.  You just install an
adapter from objects x of class X to protocol Y that receives x as
an argument and whose body is just "return x" -- that's all.

You may consider the adaptation mechanism too general to bend it to
this purpose, but I look at it differently, namely: what's the gain that
would justify a further, special-purpose mechanism that's usable only
(e.g.) when all of X's methods already have the right name and order
of parameters, but then we'd have to switch to another if there is
renaming or reordering to be done?  Unless some huge gain can be
shown to come from having multiple mechanisms, I'd rather have
just one -- "entities must not be multiplied without necessity".

Should some caller, for some weird reason, need to distinguish
whether object x was adapter to Y through an actual wrapper, or
without the need for one, the caller can test "if x is adapt(x, Y):" --
I can't easily think of actual use cases, but, if there are any,
they are covered anyway.

Incldentally, I consider the best compile-time equivalent of adaptation
I know to be Haskell's "instance" statement.  Don't let the name
mislead you -- Haskell is FP, not OO, and doesn't use "instance"
to talk about what in Python we'd call instances of a type.  Rather,
Haskell uses statement "instance" to assert that a type T is an
instance of a typeclass C.  A typeclass is Haskell's equivalent of
an interface (actually of a stateless abstract class, and then some,
but that's another issue), and "type T instances typeclass C" is
Haskell's way ot say "type T implements interface C".

Renaming IS generally necessary.  If you have an installation of
the Haskell interpreter HUGS (comes with many Linux distros,
for example -- can be downloaded from www.haskell.org also
for Windows), have a look at demos/Lattice.hs -- you may find
it readable even without knowing Haskell, since Haskell uses
significant whitespace much like Python and has much notation
in common with maths and other FP languages,  Lattice.hs
defines a typeclass "Lattice", and asserts that Bool instances
Lattice (then goes on from there, of course, but let's stop to
this part).  But of course the key functions (would be methods
for us, in an OO language) in Lattice are called meet and join
(standard math terms, after all), while in Bool the corresponding
functionality is given by functions named && and ||.  No problem,
of course: the instance statement is (MUST be!) able to
"rename" -- to assert that, when using Bool as a Lattice,
meet means && and join means || .

Since instance is a compile-time thing, it doesn't need any
'wrapper' -- just some appendix to the compiler's symbol tables,
of course.  But if we want to remain OO, dynamic, and do name
dispatching of methods, we WOULD need a wrapper of some
kind to perform the same renaming.

A facility that is SO special-purpose that it doesn't let me say
"I have conceived this new interface Lattice, and existing class bool 
is an example of it" -- or forces me to distort Lattice's method names
away from standards such as meet and join in order to fit them to
the preexisting names of bool's methods/operators (and then how
will I go about asserting that OTHER classes are also lattices...?),
does not seem a good idea to me.

> Adaptation is cool, but I don't see it as a replacement for anything that
> interfaces are supposed to achieve. Effective adaptation requires some
> kind of interface definition mechanism to work on top of.

The latter is a widespread opinion, but one from which I disagree.

Using types as the "protocols" that adaptation works with is, IMHO,
quite workable.  And some of adaptation's aspects provide facilities,
such as "third-party adapters" also working for renaming and
similar issues, without which you could not achieve all "that interfaces
are supposed to achieve" -- and I don't think those aspects should
ALSO be duplicated by adding other mechanisms AS WELL AS
adaptation.  It seems to me Zope3 has it right in this respect (even
though I think I disagree on other design choices -- I won't know
for sure until I get a chance to try it out in production code), by
making adaptation a key part of the interfaces' mechanisms.