[Types-sig] Interface PEP

Sverker Nilsson sverker.is@home.se
Tue, 27 Mar 2001 01:50:37 +0200


I think I made a mistake in responding to the previous mail. I am
requoting the question I answered now to get it in context... and
trying again.  I'm not sure what this changes either way.

> Fri, 23 Mar 2001 17:41:07 +0100, Sverker Nilsson <sverker.is@home.se> pisze:
> 
> > def f(x:y)
> >
> > The question is: what is the y.
> >
> > 1) 'interface'
> > 2) 'type'
> > 3)  something else
> 
> If you call it 'type', it would be confusing, because the builtin
> function called 'type' would no longer return the type of the object
> (for the new meaning of the term 'type').

It will return a type of the object. It is the type the object is able
to declare about itself. In the body of f, this type can be a subtype
of y but it is not necessary. The dynamic type checking also allows
for objects that don't declare themselves enough specifically. In that
case, the type object y can check the value of the object to make sure
its value is compatible with y's requirement. This is achieved by the
dynamic type checker calling a .check() method of the type object.

The checking via .check() is typicall slower than checking the subtype
relation, which I imagine can be stored as a transitively-closed
graph. Check for inclusion in that graph would be just as fast as a
dict lookup or two.

Except for speed another reason for using type() would be to be able
to specify things that can otherwise not be checked in the object.

This can, however, also be implemented with a question to the object:
'Do you implement this interface'. That seems to be more general and
should be one possibility, maybe the preferred one.

A reason to be able to use type() anyway could be that it is
convinient, allows for compatibility with programs that use type(), and
allows for a fast, standardized type check via the subtype relation.

However the checks are made, what is ensured when entering the body of
f,
will be that:

isinstance(x, y)

holds.

Requoting for context.

> If you call it 'type', it would be confusing, because the builtin

I don't see how it is confusing. It returns a type which is what you
can put in y in the example. Sometimes it's enough for type checking,
sometimes not, depending on how specific the object is about type().

Calling it 'interface' but allow for types like IntType could also
be confusing, or more so, IMHO.

Now quoting from the  new  mail.

Marcin 'Qrczak' Kowalczyk wrote:
> 
> Mon, 26 Mar 2001 18:34:47 +0200, Sverker Nilsson <sverker.is@home.se> pisze:
> 
> > > > def f(x:y)
> 
> > Seems you lost me here... It will actually return a type, 'the' type,
> > for all I can tell. In the body of the function f defined above,
> > type(x) will be a type that is a subtype of y.
> 
> There is no subtyping. I see no even a candidate for that relation.
> Types in the Python's sense are not ordered, types in your sense
> (i.e. interfaces in my sense) are not ordered either. What do you
> call subtyping in the context of Python?

If we invent a type SequenceType, I'd imagine some of the following
subtypes.

subtype(ListType, SequenceType)
subtype(TupleType, SequenceType)
subtype(ArrayType, SequenceType)
subtype(XRangeType, SequenceType)

Thus, declaring f(x:SequenceType) makes sure x has declared itself to
be one of the above, in the absense of further declarations and no
SequenceType.check() method.

Maybe we'd like an IntegralType that is either Int or Long. Thus:

subtype(IntType, IntegralType)
subtype(LongType, IntegralType)

Declaring f(x:IntegralType) then makes sure x is Int or Long, or
declares to be usable as such.

If we'd like to have a range constraint on IntegralTypes, we could
invent a IntegralRangeType. These subtypes should be generated
automatically by the constructor of IntegralRangeType.

subtype(IntegralRangeType(0,10), IntegralType)
subtype(IntegralRangeType(0,100), IntegralType)
subtype(IntegralRangeType(0,10), IntegralRangeType(0,100))

IntegralRangeType should have a .check() method.

Declaring f(x:IntegralRangeType(0,100)) makes sure x declared itself
to be integral in (0,10) or (0,100), or check(x) made sure it was of
IntegralType within that range. - This might involve calling
IntegralType.check() if it had any, or else checking subtype
relation between type(x) and IntegralType, and then checking the
value to be within the range.

> 
> Things to which 'y' belongs are disjoint with things 'type(x)'
> returns (unless you allow the shortcut of using a class or type
> as interface directly, without obtaining interface generated from
> a type explicitly, but it's a special case and works only in one
> direction: types/classes can be used as interfaces, arbitrary
> interfaces can't be used as types/classes).

This seems a bit backward now.  The only thing you can't use interfaces
for is to inherit from them - onless they somehow generated a default
class. I'm not saying we could inherit from any arbitrary 'type',
either. It's just that some types can generate an implementation
(default or not), some don't.

> 
> There is a relation of conformance, but it's between objects and
> interfaces. It often depends only on the type or class of the object,
> so it's usually a relation between types/classes and interfaces.

Between subtypes and supertypes I'd say; I still dont see the
difference.

Also between objects and types. That's why we have the .check()
method. That's what interfaces would have too, it seems to me.

> 
> If I replaced "interface" with "type" in the above two paragraphs,
> I would get complete nonsense. So you cannot artificially put types

In the first of the two paragraphs above you are talking about
differences between types and interfaces so it wouldn't make much
sense to talk about differences between type and type. I'm not
sure what else you may refer to.

In the second of those paragraphs I can get something that makes
sense to me at least. Removing 'class' also, I get:

! There is a relation of conformance, but it's between objects and
! types. It often depends only on the type of the object,
! so it's usually a relation between types and types.

Relation between types and types, makes sense to me. It's what
I call the subtype relation.

> and interfaces in one bag. All the time you would have to distinguish
> when you mean a type in the sense of "template of object definition"
> or "description of how an object can be used". They are very different
> concepts.

I'd say some types can be used for object definition, some not,
although one may think of requiring some default implementation to be
specified for all types. - However, weren't you talking about having
some interfaces being able to generate implementation, too?  That
seems to beg the question.

> When I am using an object, I'm not interested in its type,
> but in supported operations and their meaning.

Ok. My claim is that that's what the type can specify but also
'implementation' if you wish.

> 
> > What type() returns is in general an over-estimate of the set of
> > allowed values. It is 'the' type that the object decides it wants
> > to tell the world it's got. As long as it is not telling us an
> > under-estimate, it is ok for the object to tell us any type that
> > it is indeed compatible with.
> 
> There is no order among types, so I don't know what do you mean by

True, not among the builtin types, I'd agree. I was talking about
generalizing them to be more loose and generic.

> over-estimate or under-estimate.

The subtype relation between types, the check() method of types, and
informal requirements would define what is an over or under-estimate.

If a type t has a .check() function, it would be underestimation if
the object didn't pass the check. It would also be an underestimation
if the object didn't conform to the informal requirements that the
type implies, but that can't be checked.

With overestimation I was talking in the the nonstrict sense, i.e. it
is the opposite to underestimation.

If any type t' is an overestimate of x, then any type t for which
subtype(t', t) holds would also be an overestimate, by my definition.


> A type unambiguously defines a set of values. Sets of values for
> different types are completely disjoint.

As long as there is no subtypes, that is.

> So there is only one possible thing type() can return:
> every object has exactly one type according to Python's definition.

I don't know where you get this notion that there can be no subtypes
in Python. The ref manual doesnt say much about what types are,
except what I quoted before and you haven't commented on that.
Subtypes are successfully (afaik) used in languages such as Ada.


> 
> OTOH an object conforms to many interfaces. You can check if a given
> object conforms to an intreface, and you can obtain operations defined
> by the interface as applying to a given conforming object (it's not
> yet clear how exactly it would look like).

Same is true for the objects I call 'types', that seems as a natural
extension of the current concept.

> 
> Sorry, I don't see what do you gain by calling two completely different
> concepts using the same name.

I'm probably just being dense after this long discussion ;-)
But they are just aspects of the same thing for me.

I have explained before what I think one would _lose_ by calling most
of the things 'interfaces'. - and that if you put an implementation
requirement in the interface, that specifies something else than the
implementation of the _interface_, it's not an interface you are
talking about anymore.

> 
> The fact that other objects use the term "type" as "interface"
> is irrelevant, because Python already uses the term "type" in a
> different meaning.

This seems to begin to go into circular discussion. You have yet to
address my comments of what the ref manual says about what types are,
otherwise I can't seem to answer more here.

> 
> > Thus it seems conceivable that of two otherwise similar objects,
> > for example of some list implementation, one could tell us it was
> > SomeListType and the other say it was some more general SequenceType.
> 
> No, it does not make sense to let type(x) *ever* return SequenceType,
> assuming that SequenceType describes the informally existing Python's
> interface of lists, tuples, strings, xranges etc.!

ok, well well..... not in this case, if it could return a more
specific type such as ListType, it should do that. See below about
asking for the type.

> 
> It's because objects conform to independent interfaces independently.
> When something is a sequence and also is storable-in-Foo-database,
> neither of these interfaces is fundamentally more important than
> the other. Neither implies the other, so they can't be related by
> subtyping.

It should define a type that is a subtype of both sequence and
storable. BTW, it seems it can do it locally without any other needing
to know its name:

class C:
    def __init__(self):
        t=Type()
	self.__type__=t
	subtype(t, sequence)
	subtype(t, storable)


This means you can use object C() to pass to (previously) defined
functions f(x:sequence) and g(x:storable) - x will pass both's type
check using the subtype relation.

> 
> type(x) cannot generally return interfaces. "Sequence" is only
> an interface - it describes how to use an object, not how it was
> defined, and is orthogonal to most other interfaces. If type(x)
> sometimes return types, and sometimes interfaces, it's inconsistent.

Well, essentially I think you are saying that you can't have an
object return an interface, or even a list of interfaces, neither
via interface() or type().

(BTW, list of types would be supported by returning a type that was
defined to be a subtype of all the types in the list - I think that is
more general than sometimes returning 1 thing and sometimes a list.)

> 
> How do you decide which of interfaces supported by an object to return?

That's a good question. I wonder what algorithm they use in the shop
if I ask for 'what interface has the hard-disk got', 'what's the
interfaces the VCR support'... my point is, the question can actually
be usefully asked an answered - even though there may be many feasible
answers!

I suppose it should be answered in a way that is deemed useful for
the asker. Despite different askers having different requirements
(maybe an extra parameter to type()) one should try to think about
what the most useful in general would be.

Then I'd say that the most useful would be to choose a valid type that
has as few subtypes as possible, so it says as much as possible of
the object, while not being an 'understimation'.

So if an object is emulating ListType, especially if it has been
derived from it or its metaclass, it makes sense to have type() return
ListType. Returning SequenceType would not be as useful and should
thus be avoided, in this case.

However, there may be cases where only the SequenceType protocol is
supported, and then that would be the right thing to return.

> 
> > To have things make sense in this case, SomeListType should have
> > been declared to be a subtype of SequenceType.
> 
> What are your definitions of "type" and "subtype"? How types are
> created and how the subtyping relation is established?

Types can be created by type constructors. The simplest ones just
return an object with an identity, not much functionality if any.
It's used only to distinguish between types.

We can also have type constructors such as IntegralRange which have
special check() methods and when constructed, makes subtype
declarations itself with respect to other types it knows about -
it should know about IntegralType and other IntegralRange types.

Subtype relations are created with subtype(x,y) where x and y are any
types. It should do some checks such as checking for cycles I suppose,
and make a transitive closure of the graph. This can be deferred to
the next call of issubtype().

> If the subtyping relation is established by explicit assertions,
> does it make sense to assert that something is a subtype of IntType?

Wouldn't this make sense?

subtype (IntRangeType(0,10), IntType)

An object claiming to be of IntRangeType also promises to be an Int.
This only seems to make sense if you can somehow inherit from IntType
or its metaclass. I don't know if this will be possible although
it doesn't seem infeasible.

> Since IntType defines the representation, IMHO it doesn't make
> sense, which implies that at least two families of objects have been
> artificially put into one bag labelled "types".

Perhaps, but maybe it is an implementation detail. The same goes
for FileType, wouldn't it. 

> 
> > > Of course there would be gains: you could inherit from files, and
> >
> > You could still do that, at least from file *classes*: you inherit
> > from something like metaclass(FileType). I thought I told you this
> > quite clearly previously!
> 
> Why to be forced to convert files to classes, instead of having them
> unified in the first place? What do you gain by their separation?

Backward compatibility with respect to type(), and allowing for more
generic types than only classes. Also, since GvR suggested
metaclass(), I suspect there can be some reasons to do with the
implementation.

What would you gain by inheriting from FileType instead of
metaclass(FileType) aka FileClass?

Sverker