[Types-sig] QueryProtocol
Marcin 'Qrczak' Kowalczyk
qrczak@knm.org.pl
24 Mar 2001 11:53:21 GMT
Fri, 23 Mar 2001 01:20:05 -0500 (EST), Clark C. Evans <cce@clarkevans.com=
> pisze:
> > Actually my view can be declarative too. It is stolen from Haskell
> > where it *is* declarative.
>=20
> By declarative, I am talking about the class "declaring" it's
> compliance with an interface, "I am a such-and-such".
My definition is similar but more general: somebody declares that a
class conforms to an interface. It's not necessarily the class itself,
and not necessarily the interface, although often it's code in the
same module as one of them.
There are three practical scenarios:
1. An interface is well known (e.g. a readable file) and a new type or
class wants to implement it. The code executed after the definition
of the class will register it.
=20
(Note that methods required from the object in order to declare it
as supporting an interface may be different from the functionality
of the interface; usually be a subset. For example it's enough to
provide readline to derive readlines. A part of the functionality
can be implemented in the interface.)
2. A type or class is well known (e.g. string) and a new interface
wants to describe it (e.g. appropriate as a function argument in
some RPC mechanism). The interface uses public methods of important
types or classes it knows about to apply its terms to them. Other
types or classes may register later according to the first scenario.
(Note that this implies that an object doesn't know all its
interfaces, in the same way as it doesn't know all its names.
It's practically meaningless to ask about all interfaces of an
object, and it should not be expected that it's able to check an
arbitrary interface in any other way than asking the interface if
it knows him. For simplicity I propose to always ask the interface.)
3. A type or class and an interface were developed separately.
It happens that the type or class provides everything needed by
the intreface. Somebody wants to use them together. So he provides
the necessary binding.
> By descriptive, I am talking about an interface figuring out that a
> class is compliant by examining it, "It looks like a such-and-such".
Me too. But it's only a special case in my view: an interface might
not require that somebody actually declared each class, but say
loudly "anybody having __getitem__ and __len__ will be blessed by me
as conforming".
It's mostly for protocols already present in Python, when it's easier
to be liberal than to require all old code to register or be registered.
Stricter interfaces will require explicit registration.
> I suppose you could define a declarative syntax whereby an interface
> can specify that a given class complies... but this is beside
> the point.
This is not beside my point.
> IMHO, they belong in three places; the class (if it declares it's
> complance with an interface), the interface (if the interface
> can deduce that the class complies with the interface description),
> and perhaps third parties who know better!
They can logically belong to three places ("where did they come from").
It doesn't prevent us from storing them in one place however ("where
will we look for them").
It will be simpler of a single approach can embrace the rest. I hate
code which tries a dozen of ways in some random order to check if an
object conforms to an interface, and answers "no" if none worked.
It's getting too complicated. Asking the interface is enough.
> > The decision about the level of checking of the conformance, and
> > details of dispatching, are up to the interface object.
>=20
> No. The decision about conformance should be up to
> the class or the interface (or possibly even the environment).
When a class registers itself in an interface, the fact that we ask
the interface is an implementation detail - it's still the class who
decided to register.
Similarly when a third party has registered. There is no need of a
central registry of all interfaces nor of a mixed ways of storing it.
The knowledge is physically distributed over interface objects, no
matter where it originated.
> > #def empty() # This doesn't work: there is no S to dispatch o=
n.
> > # It works in Haskell where it's enough to dispa=
tch
> > # on the type of the return value, because it's =
done
> > # statically.
>=20
> Well, if you said that an interface is a type of module, you can
> have static methods...
But they don't know which class is expected to be returned. It's
obvious how one would use it in this approach, but it's unimplementable.
You can't dispatch on the return value in Python.
> > # A function which uses the Sequence interface in a generic way:
> > def join(*args):
> > return reduce(Sequence.concat, args)
> > # I would like to say:
> > # return reduce(Sequence.concat, args, empty())
> > # The empty() case doesn't work. It won't work in current Pyt=
hon
> > # because the empty list of strings is indistinguishable from
> > # the empty list of integer lists.
>=20
> Lost me here...
Let's say I have a list of sequences. (The fact that it's a list is
irrelevant; it can be any sequence. The point is the type of sequences
which are the items.)
I know that each of these sequences is the same kind of sequence,
i.e. that the list is homogeneous. I don't care that it's not enforced
- this is yet another issue.
I want to concatenate all the sequences. Of course if they are strings,
I want to get a string; if they are Unicode strings, I want to get
a Unicode string; similarly for lists, tuples, and any other kind
of sequence.
The point is: what to do with the empty list? It can be an empty list
of strings, or an empty list of lists, or whatever. I can't tell.
I don't know what kind of empty sequence to return. I'm stuck.
There are two places which could give a hint, but neither works.
The first is the list: it's empty, but it was *meant* to be a list
of strings, or of lists, or whatever. If it stored the type of items
somewhere, I could get it.
The second place is the context of the call. Somebody expects me
to return a string, or a list, or whatever. But I can't know it
in advance.
I could at most try to return a special object which will mimic an
empty sequence of an arbitrary kind. E.g. concatenating it with
a string gives the same string.
Note that it does work in Haskell. The idea of "examining the type"
is very different (I don't check an object at runtime but I'm
automatically dispatched according to declared types).
It works in both of the above senses: the meaning is dispatched on
the type of the argument (an empty list of strings has a different
type than an empty list of lists) and on the context of usage.
There is a compile error if they disagree.
Knowing the concrete type in either of the places (what is constructed
for an argument or what type is expected on the return) disambiguates
the other. Only when both are unspecified (I have an unspecified
kind of sequence, e.g. a generic "empty sequence", and I pass it to
a function which accepts an arbitrary kind of sequence), there is an
ambiguity error - nowhere is determined what actual kind of sequence
it is.
I don't propose to make Python look like Haskell; it's just an
opportunity to explain that static typing is not limited to checking
validity of arguments, only statically enforced. It is used to express
programs, such that erasing all the types loses important information.
There are other cases besides the empty list case when the type of an
object is not derivable from its physical contents (most important
are the monadic 'return' function and numeric literals). They all
work according to the same rules of overloading / dispatching, which
are not limited not only to a distinguished argument of a function,
but even to argument types at all. A function can be dispatched on
"the type of items of the sequence it will return".
--=20
__("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/
\__/
^^ SYGNATURA ZAST=CAPCZA
QRCZAK