[Types-sig] QueryProtocol

Clark C. Evans cce@clarkevans.com
Tue, 20 Mar 2001 18:56:11 -0500 (EST)


On Mon, 19 Mar 2001, Paul Prescod wrote:
> QueryProtocol is an idea deserving of a PEP. I think it is interesting
> but I would like to see it commonly used as a convention in Python
> programming before it became a part of the language.

Ok.  I hope this is adequate.  ;) Clark

---------- Forwarded message ----------
Date: Tue, 20 Mar 2001 18:43:48 -0500 (EST)
From: Clark C. Evans <cce@clarkevans.com>
To: python-list@python.org
Newsgroups: comp.lang.python
Subject: Yet Another PEP:  Query Protocol Interface or __query__

Please have a look at the PEP below, it is intended to be a
light-weight solution to "typing" based on programmer intent.

Regards,

Clark Evans

PEP: XXX
Title: Query Interface Protocol
Version: $Revision$
Author: Clark Evans
Python-Version: 2.2
Status: Active
Type: Standards Track
Created: 21-Mar-2001

Summary

    This paper asserts that "interface typing" can be carved
    into two separable concerns, that of (a) protocol, which is
    all about behavior/expectations, and that of (b) signature
    which is about method existence and argument type checking.

    This proposal puts forth a declarative method for interface
    protocol discovery that could be orthogonal and complementary
    to a more signature based approach as being developed by the
    types special interest group.

Context

    Python is a very dynamic language with powerful introspection
    capabilities.  However, it has yet to formalize an interface
    or abstract type checking mechanism. A consensus for a user
    defined type system may be far off into the future.

    Currently, existence of particular methods, particularly those
    that are built-in such as __getitem__, is used as an indicator
    of support for a particular interface.  This method may work
    for interfaces blessed by GvR, such as the new enumerator interface
    being proposed and identified by a new built-in __iter__.

    However, this current method does not admit an infallible way
    to identify interfaces lacking a built-in method.

Motivation

    In the recent type special interest group discussion [1], there
    were two complementary quotes which motivated this proposal:

       "The deep(er) part is whether the object passed in thinks of
        itself as implementing the Foo interface. This means that
        its author has (presumably) spent at least a little time
        about the invariants that a Foo should obey." - GvR [2]

    and

       "There is no concept of asking an object which interface it
        implements. There is no "the" interface it implements. It's
        not even a set of interfaces, because the object doesn't
        know them in advance. Interfaces can be defined after objects
        conforming to them are created." -- Marcin Kowalczyk [3]

    The first quote focuses on the intent of a class, including
    not only the existence of particular methods, but more
    importantly the call sequence, behavior, and other invariants.
    Where the second quote focuses on the type signature of the
    class.  These quotes motivate a distinction between interface
    as a "declarative, I am a such-and-such" construct, as opposed
    to a "descriptive, It looks like a such-and-such" mechanism.

    Furthermore, it is clear that both aspects of interface are
    important, if not completely orthogonal and complementary.
    For purposes of this proposal, the word "protocol" is aligned
    with Guido's deep issue, and "signature" with Marcin's vision
    of a type system.  In this way, we clearly demarcate the two
    attitudes of the word "interface" in a type system.

    Clearly, detailing an exhaustive method of interface
    signatures is very difficult problem, especially for
    a dynamic language like Python where the interface
    signature for a class may change over time.  However, a
    simple declarative mechanism to inquire which interface
    protocols an object supports at a given time is relatively
    straight forward and could bring immediate benefit.

Details

    A new built-in method, __query__ is proposed.  This method has
    a single argument, an interface protocol identifier, and either
    returns an object supporting the given protocol, or throws an
    unknown exception.

    An interface protocol identifier is a lower-case string having
    a reverse DNS host name string:   TLD '.' DOMAIN ['.' NAME ]*
    Where TLD is a top level domain such as 'com' or 'uk.co'.  DOMAIN
    is a registered name in the given TLD.  And one or more optional
    NAME separated by a period.  NAME is a sequence of one or more
    alphabetic characters including the dash '-' character.  See the
    relevant ITEF specifications for specific details.

    Note that all of the protocols above have at least one period.
    In the future it may be prudent to introduce one or more "blessed"
    interface protocol identifiers which do not have a period. Potential
    examples include "enumerator", "sequence", "map", "string", etc.

    In addition, a built-in query method could be introduced that
    calls the __query__ method on a given object.

Example Usage

    >>> class EggsOnly:
            def eggs(self,str): print "eggs!" + str
            def __query__(self,protocol):
                if protocol == "org.python.example.eggs": return self
                raise "unknown: " + protocol

    >>> class EggsAndHam:
            def ham(self,str): print "ham!"+str
            def __query__(self,protocol):
                if protocol == "org.python.example.ham": return self
                if protocol == "org.python.example.eggs":
                    return EggsOnly()
                raise "unknown" + protocol

    >>> x = EggsOnly()
    >>> x.eggs("hello")
    eggs!hello
    >>> x.__query__("org.python.example.eggs").eggs("hello")
    eggs!hello

    >>> z = EggsAndHam()
    >>> z.ham("hi")
    ham!hi
    >>> z.__query__("org.python.example.ham").ham("hi")
    ham!hi
    >>> z.__query__("org.python.example.eggs").eggs("hi")
    eggs!hi

    >>> y = x.__query__("org.python.bad")
    Traceback (innermost last):
      File "<interactive input>", line 1, in ?
      File "<interactive input>", line 5, in __query__
        unknown: org.python.bad

Relationships:

    The iterator special interest group is proposing a new built-in
    called "__iter__", which could be replaced with __query__ and
    a blessed interface protocol identifier of "enumerator".
    Therefore calls to  obj.__iter__() could be replaced with
    obj.__query__("enumerator") with no semantic difference.

    Although this proposal may sounds similar to Microsoft's
    QueryInterface, it differs by a number of aspects.  First,
    there is not a special "IUnknown" interface which can be used
    for object identity, although this could be proposed as one
    of those "special" blessed interface protocol identifiers.
    Second, with QueryInterface, once an object supports a particular
    interface it must always there after support this interface;
    this proposal makes no such guarantee, although this may be
    added at a later time. Third, implementations of Microsoft's
    QueryInterface must support a kind of equivalence relation.
    By reflexive they mean the querying an interface for itself
    must always succeed.  By symmetrical they mean that if one
    can successfully query an interface IA for a second interface
    IB, then one must also be able to successfully query the
    interface IB for IA.  And finally, by transitive they mean if
    one can successfully query IA for IB and one can successfully
    query IB for IC, then one must be able to successfully query
    IA for IC.  Ability to support this type of equivalence relation
    should be encouraged, but may not be possible.  Further research
    on this topic (by someone familiar with Microsoft COM) would be
    helpful in further determining how compatible this proposal is.

Backwards Compatibility

    There should be no problem with backwards compatibility.
    Indeed this proposal, save an built-in query() function,
    could be tested without changes to the interpreter.

Future Compatibility

    It appears that this proposal could be implemented orthogonal
    to the protocol checking system being constructed [4] by Paul
    Prescod and company.  It may not be all that compatible with
    the more ambitious signature declaration and checking approach[5]
    taken by Michel Pelletier.  This requires further investigation.

Questions and Answers

    Q:  This is just a type-coercion proposal.

    A:  No. Certainly it could be used for type-coercion, such
        coercion would be explicit via __query__ or query function.
        Of course, if this was used for iterator interface, then the
        for construct may do an implicit __query__("enumerator") but
        this would be an exception rather than the rule.

    Q:  Why did the author write this PEP?

    A:  He wanted a simple proposal that covered the "deep part" of
        interfaces without getting tied up in signature woes.  Also, it
        was clear that __iter__ proposal put forth is just an example
        of this type of interface.  Further, the author is doing XML
        based client server work, and wants to write generic tree based
        algorithms that work on particular interfaces and would
        like these algorithms to be used by anyone willing to make
        an "adapter" having the interface required by the algorithm.

    Q:  Why not call this __queryinterface__ ?

    A:  Too close to Microsoft's QueryInterface, especially given the
        semantic differences which may not be reconcilable.

    Q:  Is this in opposition to the type special interest group?

    A:  No.  It is meant as a simple, need based solution that could
        easily complement the efforts by that group.

Copyright

    This document has been placed in the public domain.


References and Footnotes

    [1] http://www.zope.org/Members/michel/types-sig/TreasureTrove
    [2] http://mail.python.org/pipermail/types-sig/2001-March/001105.html
    [3] http://mail.python.org/pipermail/types-sig/2001-March/001206.html
    [4] http://mail.python.org/pipermail/types-sig/2001-March/001223.html