[Python-checkins] r55028 - peps/trunk/pep-0000.txt peps/trunk/pep-3124.txt

phillip.eby python-checkins at python.org
Tue May 1 00:44:40 CEST 2007


Author: phillip.eby
Date: Tue May  1 00:44:38 2007
New Revision: 55028

Added:
   peps/trunk/pep-3124.txt   (contents, props changed)
Modified:
   peps/trunk/pep-0000.txt
Log:
Rough first draft of generic function PEP


Modified: peps/trunk/pep-0000.txt
==============================================================================
--- peps/trunk/pep-0000.txt	(original)
+++ peps/trunk/pep-0000.txt	Tue May  1 00:44:38 2007
@@ -120,6 +120,7 @@
  S  3120  Using UTF-8 as the default source encoding   von Löwis
  S  3121  Module Initialization and finalization       von Löwis
  S  3123  Making PyObject_HEAD conform to standard C   von Löwis
+ S  3124  Overloading, Generic Functions, Interfaces   Eby
  S  3141  A Type Hierarchy for Numbers                 Yasskin
 
  Finished PEPs (done, implemented in Subversion)
@@ -482,6 +483,7 @@
  S  3121  Module Initialization and finalization       von Löwis
  SR 3122  Delineation of the main module               Cannon
  S  3123  Making PyObject_HEAD conform to standard C   von Löwis
+ S  3124  Overloading, Generic Functions, Interfaces   Eby
  S  3141  A Type Hierarchy for Numbers                 Yasskin
 
 

Added: peps/trunk/pep-3124.txt
==============================================================================
--- (empty file)
+++ peps/trunk/pep-3124.txt	Tue May  1 00:44:38 2007
@@ -0,0 +1,908 @@
+PEP: 3124
+Title: Overloading, Generic Functions, Interfaces, and Adaptation
+Version: $Revision: 52916 $
+Last-Modified: $Date: 2006-12-04 14:59:42 -0500 (Mon, 04 Dec 2006) $
+Author: Phillip J. Eby <pje at telecommunity.com>
+Discussions-To: Python 3000 List <python-3000 at python.org>
+Status: Draft
+Type: Standards Track
+Requires: 3107, 3115, 3119
+Replaces: 245, 246
+Content-Type: text/x-rst
+Created: 28-Apr-2007
+Post-History: 30-Apr-2007
+
+
+Abstract
+========
+
+This PEP proposes a new standard library module, ``overloading``, to
+provide generic programming features including dynamic overloading
+(aka generic functions), interfaces, adaptation, method combining (ala
+CLOS and AspectJ), and simple forms of aspect-oriented programming.
+
+The proposed API is also open to extension; that is, it will be
+possible for library developers to implement their own specialized
+interface types, generic function dispatchers, method combination
+algorithms, etc., and those extensions will be treated as first-class
+citizens by the proposed API.
+
+The API will be implemented in pure Python with no C, but may have
+some dependency on CPython-specific features such as ``sys._getframe``
+and the ``func_code`` attribute of functions.  It is expected that
+e.g. Jython and IronPython will have other ways of implementing
+similar functionality (perhaps using Java or C#).
+
+
+Rationale and Goals
+===================
+
+Python has always provided a variety of built-in and standard-library
+generic functions, such as ``len()``, ``iter()``, ``pprint.pprint()``,
+and most of the functions in the ``operator`` module.  However, it
+currently:
+
+1. does not have a simple or straightforward way for developers to
+   create new generic functions,
+
+2. does not have a standard way for methods to be added to existing
+   generic functions (i.e., some are added using registration
+   functions, others require defining ``__special__`` methods,
+   possibly by monkeypatching), and
+
+3. does not allow dispatching on multiple argument types (except in
+   a limited form for arithmetic operators, where "right-hand"
+   (``__r*__``) methods can be used to do two-argument dispatch.
+
+In addition, it is currently a common anti-pattern for Python code
+to inspect the types of received arguments, in order to decide what
+to do with the objects.  For example, code may wish to accept either
+an object of some type, or a sequence of objects of that type.
+
+Currently, the "obvious way" to do this is by type inspection, but
+this is brittle and closed to extension.  A developer using an
+already-written library may be unable to change how their objects are
+treated by such code, especially if the objects they are using were
+created by a third party.
+
+Therefore, this PEP proposes a standard library module to address
+these, and related issues, using decorators and argument annotations
+(PEP 3107).  The primary features to be provided are:
+
+* a dynamic overloading facility, similar to the static overloading
+  found in languages such as Java and C++, but including optional
+  method combination features as found in CLOS and AspectJ.
+
+* a simple "interfaces and adaptation" library inspired by Haskell's
+  typeclasses (but more dynamic, and without any static type-checking),
+  with an extension API to allow registering user-defined interface
+  types such as those found in PyProtocols and Zope.
+
+* a simple "aspect" implementation to make it easy to create stateful
+  adapters and to do other stateful AOP.
+
+These features are to be provided in such a way that extended
+implementations can be created and used.  For example, it should be
+possible for libraries to define new dispatching criteria for
+generic functions, and new kinds of interfaces, and use them in
+place of the predefined features.  For example, it should be possible
+to use a ``zope.interface`` interface object to specify the desired
+type of a function argument, as long as the ``zope.interface``
+registered itself correctly (or a third party did the registration).
+
+In this way, the proposed API simply offers a uniform way of accessing
+the functionality within its scope, rather than prescribing a single
+implementation to be used for all libraries, frameworks, and
+applications.
+
+
+User API
+========
+
+The overloading API will be implemented as a single module, named
+``overloading``, providing the following features:
+
+
+Overloading/Generic Functions
+-----------------------------
+
+The ``@overload`` decorator allows you to define alternate
+implementations of a function, specialized by argument type(s).  A
+function with the same name must already exist in the local namespace.
+The existing function is modified in-place by the decorator to add
+the new implementation, and the modified function is returned by the
+decorator.  Thus, the following code::
+
+    from overloading import overload
+    from collections import Iterable
+    
+    def flatten(ob):
+        """Flatten an object to its component iterables"""
+        yield ob
+
+    @overload
+    def flatten(ob: Iterable):
+        for o in ob:
+            for ob in flatten(o):
+                yield ob
+
+    @overload
+    def flatten(ob: basestring):
+        yield ob
+
+creates a single ``flatten()`` function whose implementation roughly
+equates to::
+
+    def flatten(ob):
+        if isinstance(ob, basestring) or not isinstance(ob, Iterable):
+            yield ob
+        else:
+            for o in ob:
+                for ob in flatten(o):
+                    yield ob
+
+**except** that the ``flatten()`` function defined by overloading
+remains open to extension by adding more overloads, while the
+hardcoded version cannot be extended.
+
+For example, if someone wants to use ``flatten()`` with a string-like
+type that doesn't subclass ``basestring``, they would be out of luck
+with the second implementation.  With the overloaded implementation,
+however, they can either write this::
+
+    @overload
+    def flatten(ob: MyString):
+        yield ob
+
+or this (to avoid copying the implementation)::
+
+    from overloading import RuleSet
+    RuleSet(flatten).copy_rules((basestring,), (MyString,))
+
+(Note also that, although PEP 3119 proposes that it should be possible
+for abstract base classes like ``Iterable`` to allow classes like
+``MyString`` to claim subclass-hood, such a claim is *global*,
+throughout the application.  In contrast, adding a specific overload
+or copying a rule is specific to an individual function, and therefore
+less likely to have undesired side effects.)
+
+
+``@overload`` vs. ``@when``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``@overload`` decorator is a common-case shorthand for the more
+general ``@when`` decorator.  It allows you to leave out the name of
+the function you are overloading, at the expense of requiring the
+target function to be in the local namespace.  It also doesn't support
+adding additional criteria besides the ones specified via argument
+annotations.  The following function definitions have identical
+effects, except for name binding side-effects (which will be described
+below)::
+
+    @overload
+    def flatten(ob: basestring):
+        yield ob
+
+    @when(flatten)
+    def flatten(ob: basestring):
+        yield ob
+
+    @when(flatten)
+    def flatten_basestring(ob: basestring):
+        yield ob
+
+    @when(flatten, (basestring,))
+    def flatten_basestring(ob):
+        yield ob
+
+The first definition above will bind ``flatten`` to whatever it was
+previously bound to.  The second will do the same, if it was already
+bound to the ``when`` decorator's first argument.  If ``flatten`` is
+unbound or bound to something else, it will be rebound to the function
+definition as given.  The last two definitions above will always bind
+``flatten_basestring`` to the function definition as given.
+
+Using this approach allows you to both give a method a descriptive
+name (often useful in tracebacks!) and to reuse the method later.
+
+Except as otherwise specified, all ``overloading`` decorators have the
+same signature and binding rules as ``@when``.  They accept a function
+and an optional "predicate" object.
+
+The default predicate implementation is a tuple of types with
+positional matching to the overloaded function's arguments.  However,
+an arbitrary number of other kinds of of predicates can be created and
+registered using the `Extension API`_, and will then be usable with
+``@when`` and other decorators created by this module (like
+``@before``, ``@after``, and ``@around``).
+
+ 
+Method Combination and Overriding
+---------------------------------
+
+When an overloaded function is invoked, the implementation with the
+signature that *most specifically matches* the calling arguments is
+the one used.  If no implementation matches, a ``NoApplicableMethods``
+error is raised.  If more than one implementation matches, but none of
+the signatures are more specific than the others, an ``AmbiguousMethods``
+error is raised.
+
+For example, the following pair of implementations are ambiguous, if
+the ``foo()`` function is ever called with two integer arguments,
+because both signatures would apply, but neither signature is more
+*specific* than the other (i.e., neither implies the other)::
+
+    def foo(bar:int, baz:object):
+        pass
+
+    @overload
+    def foo(bar:object, baz:int):
+        pass
+
+In contrast, the following pair of implementations can never be
+ambiguous, because one signature always implies the other; the
+``int/int`` signature is more specific than the ``object/object``
+signature::
+
+    def foo(bar:object, baz:object):
+        pass
+
+    @overload
+    def foo(bar:int, baz:int):
+        pass
+
+A signature S1 implies another signature S2, if whenever S1 would
+apply, S2 would also.  A signature S1 is "more specific" than another
+signature S2, if S1 implies S2, but S2 does not imply S1.
+
+Although the examples above have all used concrete or abstract types
+as argument annotations, there is no requirement that the annotations
+be such.  They can also be "interface" objects (discussed in the
+`Interfaces and Adaptation`_ section), including user-defined
+interface types.  (They can also be other objects whose types are
+appropriately registered via  the `Extension API`_.)
+
+
+Proceeding to the "Next" Method
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the first parameter of an overloaded function is named
+``__proceed__``, it will be passed a callable representing the next
+most-specific method.  For example, this code::
+
+    def foo(bar:object, baz:object):
+        print "got objects!"
+
+    @overload
+    def foo(__proceed__, bar:int, baz:int):
+        print "got integers!"
+        return __proceed__(bar, baz)
+
+Will print "got integers!" followed by "got objects!".
+
+If there is no next most-specific method, ``__proceed__`` will be
+bound to a ``NoApplicableMethods`` instance.  When called, a new
+``NoApplicableMethods`` instance will be raised, with the arguments
+passed to the first instance.
+
+Similarly, if the next most-specific methods have ambiguous precedence
+with respect to each other, ``__proceed__`` will be bound to an
+``AmbiguousMethods`` instance, and if called, it will raise a new
+instance.
+
+Thus, a method can either check if ``__proceed__`` is an error
+instance, or simply invoke it.  The ``NoApplicableMethods`` and
+``AmbiguousMethods`` error classes have a common ``DispatchError``
+base class, so ``isinstance(__proceed__, overloading.DispatchError)``
+is sufficient to identify whether ``__proceed__`` can be safely
+called.
+
+(Implementation note: using a magic argument name like ``__proceed__``
+could potentially be replaced by a magic function that would be called
+to obtain the next method.  A magic function, however, would degrade
+performance and might be more difficult to implement on non-CPython
+platforms.  Method chaining via magic argument names, however, can be
+efficiently implemented on any Python platform that supports creating
+bound methods from functions -- one simply recursively binds each
+function to be chained, using the following function or error as the
+``im_self`` of the bound method.)
+
+
+"Before" and "After" Methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to the simple next-method chaining shown above, it is
+sometimes useful to have other ways of combining methods.  For
+example, the "observer pattern" can sometimes be implemented by adding
+extra methods to a function, that execute before or after the normal
+implementation.
+
+To support these use cases, the ``overloading`` module will supply
+``@before``, ``@after``, and ``@around`` decorators, that roughly
+correspond to the same types of methods in the Common Lisp Object
+System (CLOS), or the corresponding "advice" types in AspectJ.
+
+Like ``@when``, all of these decorators must be passed the function to
+be overloaded, and can optionally accept a predicate as well::
+
+    def begin_transaction(db):
+        print "Beginning the actual transaction"
+
+
+    @before(begin_transaction)
+    def check_single_access(db: SingletonDB):
+        if db.inuse:
+            raise TransactionError("Database already in use")
+
+    @after(begin_transaction)
+    def start_logging(db: LoggableDB):
+        db.set_log_level(VERBOSE)
+
+
+``@before`` and ``@after`` methods are invoked either before or after
+the main function body, and are *never considered ambiguous*.  That
+is, it will not cause any errors to have multiple "before" or "after"
+methods with identical or overlapping signatures.  Ambiguities are
+resolved using the order in which the methods were added to the
+target function.  
+
+"Before" methods are invoked most-specific method first, with
+ambiguous methods being executed in the order they were added.  All
+"before" methods are called before any of the function's "primary"
+methods (i.e. normal ``@overload`` methods) are executed.
+
+"After" methods are invoked in the *reverse* order, after all of the
+function's "primary" methods are executed.  That is, they are executed
+least-specific methods first, with ambiguous methods being executed in
+the reverse of the order in which they were added.
+
+The return values of both "before" and "after" methods are ignored,
+and any uncaught exceptions raised by *any* methods (primary or other)
+immediately end the dispatching process.  "Before" and "after" methods
+cannot have ``__proceed__`` arguments, as they are not responsible
+for calling any other methods.  They are simply called as a
+notification before or after the primary methods.
+
+Thus, "before" and "after" methods can be used to check or establish
+preconditions (e.g. by raising an error if the conditions aren't met)
+or to ensure postconditions, without needing to duplicate any existing
+functionality.
+
+
+"Around" Methods
+~~~~~~~~~~~~~~~~
+
+The ``@around`` decorator declares a method as an "around" method.
+"Around" methods are much like primary methods, except that the
+least-specific "around" method has higher precedence than the
+most-specific "before" or method.
+
+Unlike "before" and "after" methods, however, "Around" methods *are*
+responsible for calling their ``__proceed__`` argument, in order to
+continue the invocation process.  "Around" methods are usually used
+to transform input arguments or return values, or to wrap specific
+cases with special error handling or try/finally conditions, e.g.::
+
+    @around(commit_transaction)
+    def lock_while_committing(__proceed__, db: SingletonDB):
+        with db.global_lock:
+            return __proceed__(db)
+
+They can also be used to replace the normal handling for a specific
+case, by *not* invoking the ``__proceed__`` function.
+
+The ``__proceed__`` given to an "around" method will either be the
+next applicable "around" method, a ``DispatchError`` instance,
+or a synthetic method object that will call all the "before" methods,
+followed by the primary method chain, followed by all the "after"
+methods, and return the result from the primary method chain.
+
+Thus, just as with normal methods, ``__proceed__`` can be checked for
+``DispatchError``-ness, or simply invoked.  The "around" method should
+return the value returned by ``__proceed__``, unless of course it
+wishes to modify or replace it with a different return value for the
+function as a whole.
+
+
+Custom Combinations
+~~~~~~~~~~~~~~~~~~~
+
+The decorators described above (``@overload``, ``@when``, ``@before``,
+``@after``, and ``@around``) collectively implement what in CLOS is
+called the "standard method combination" -- the most common patterns
+used in combining methods.
+
+Sometimes, however, an application or library may have use for a more
+sophisticated type of method combination.  For example, if you
+would like to have "discount" methods that return a percentage off,
+to be subtracted from the value returned by the primary method(s),
+you might write something like this::
+
+    from overloading import always_overrides, merge_by_default
+    from overloading import Around, Before, After, Method, MethodList
+
+    class Discount(MethodList):
+        """Apply return values as discounts"""
+    
+        def __call__(self, *args, **kw):
+            retval = self.tail(*args, **kw)
+            for sig, body in self.sorted():
+                retval -= retval * body(*args, **kw)
+            return retval
+
+    # merge discounts by priority
+    merge_by_default(Discount)
+
+    # discounts have precedence over before/after/primary methods
+    always_overrides(Discount, Before)
+    always_overrides(Discount, After)
+    always_overrides(Discount, Method)
+
+    # but not over "around" methods
+    always_overrides(Around, Discount)
+
+    # Make a decorator called "discount" that works just like the 
+    # standard decorators...
+    discount = Discount.make_decorator('discount')
+
+    # and now let's use it...
+    def price(product):
+        return product.list_price
+
+    @discount(price)
+    def ten_percent_off_shoes(product: Shoe)
+        return Decimal('0.1')
+
+Similar techniques can be used to implement a wide variety of
+CLOS-style method qualifiers and combination rules.  The process of
+creating custom method combination objects and their corresponding
+decorators is described in more detail under the `Extension API`_
+section.
+
+Note, by the way, that the ``@discount`` decorator shown will work
+correctly with any new predicates defined by other code.  For example,
+if ``zope.interface`` were to register its interface types to work
+correctly as argument annotations, you would be able to specify
+discounts on the basis of its interface types, not just classes or
+``overloading``-defined interface types.
+
+Similarly, if a library like RuleDispatch or PEAK-Rules were to
+register an appropriate predicate implementation and dispatch engine,
+one would then be able to use those predicates for discounts as well,
+e.g.::
+
+    from somewhere import Pred  # some predicate implementation
+    
+    @discount(
+        price,
+        Pred("isinstance(product,Shoe) and"
+             " product.material.name=='Blue Suede'")
+    )
+    def forty_off_blue_suede_shoes(product):
+        return Decimal('0.4')
+
+The process of defining custom predicate types and dispatching engines
+is also described in more detail under the `Extension API`_ section.
+
+
+Overloading Inside Classes
+--------------------------
+
+All of the decorators above have a special additional behavior when
+they are directly invoked within a class body: the first parameter
+(other than ``__proceed__``, if present) of the decorated function
+will be treated as though it had an annotation equal to the class
+in which it was defined.
+
+That is, this code::
+
+    class And(object):
+        # ...
+        @when(get_conjuncts)
+        def __conjuncts(self):
+            return self.conjuncts
+
+produces the same effect as this (apart from the existence of a
+private method)::
+
+    class And(object):
+        # ...
+
+    @when(get_conjuncts)
+    def get_conjuncts_of_and(ob: And):
+        return ob.conjuncts    
+
+This behavior is both a convenience enhancement when defining lots of
+methods, and a requirement for safely distinguishing multi-argument
+overloads in subclasses.  Consider, for example, the following code::
+        
+    class A(object):
+        def foo(self, ob):
+            print "got an object"
+
+        @overload
+        def foo(__proceed__, self, ob:Iterable):
+            print "it's iterable!"
+            return __proceed__(self, ob)
+
+
+    class B(A):
+        foo = A.foo     # foo must be defined in local namespace
+
+        @overload
+        def foo(__proceed__, self, ob:Iterable):
+            print "B got an iterable!"
+            return __proceed__(self, ob)
+
+Due to the implicit class rule, calling ``B().foo([])`` will print
+"B got an iterable!" followed by "it's iterable!", and finally,
+"got an object", while ``A().foo([])`` would print only the messages
+defined in ``A``.
+
+Conversely, without the implicit class rule, the two "Iterable"
+methods would have the exact same applicability conditions, so calling
+either ``A().foo([])`` or ``B().foo([])`` would result in an
+``AmbiguousMethods`` error.
+
+It is currently an open issue to determine the best way to implement
+this rule in Python 3.0.  Under Python 2.x, a class' metaclass was
+not chosen until the end of the class body, which means that
+decorators could insert a custom metaclass to do processing of this
+sort.  (This is how RuleDispatch, for example, implements the implicit
+class rule.)
+
+PEP 3115, however, requires that a class' metaclass be determined
+*before* the class body has executed, making it impossible to use this
+technique for class decoration any more.
+
+At this writing, discussion on this issue is ongoing.
+
+
+Interfaces and Adaptation
+-------------------------
+
+The ``overloading`` module provides a simple implementation of
+interfaces and adaptation.  The following example defines an
+``IStack`` interface, and declares that ``list`` objects support it::
+
+    from overloading import abstract, Interface
+
+    class IStack(Interface):
+        @abstract
+        def push(self, ob)
+            """Push 'ob' onto the stack"""
+
+        @abstract
+        def pop(self):
+            """Pop a value and return it"""
+
+
+    when(IStack.push, (list, object))(list.append)
+    when(IStack.pop, (list,))(list.pop)
+
+    mylist = []
+    mystack = IStack(mylist)
+    mystack.push(42)
+    assert mystack.pop()==42
+
+The ``Interface`` class is a kind of "universal adapter".  It accepts
+a single argument: an object to adapt.  It then binds all its methods
+to the target object, in place of itself.  Thus, calling
+``mystack.push(42``) is the same as calling
+``IStack.push(mylist, 42)``.
+
+The ``@abstract`` decorator marks a function as being abstract: i.e.,
+having no implementation.  If an ``@abstract`` function is called,
+it raises ``NoApplicableMethods``.  To become executable, overloaded
+methods must be added using the techniques previously described. (That
+is, methods can be added using ``@when``, ``@before``, ``@after``,
+``@around``, or any custom method combination decorators.)
+
+In the example above, the ``list.append`` method is added as a method
+for ``IStack.push()`` when its arguments are a list and an arbitrary
+object.  Thus, ``IStack.push(mylist, 42)`` is translated to
+``list.append(mylist, 42)``, thereby implementing the desired
+operation.
+
+(Note: the ``@abstract`` decorator is not limited to use in interface
+definitions; it can be used anywhere that you wish to create an
+"empty" generic function that initially has no methods.  In
+particular, it need not be used inside a class.)
+
+
+Subclassing and Re-assembly
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Interfaces can be subclassed::
+
+    class ISizedStack(IStack):
+        @abstract
+        def __len__(self):
+            """Return the number of items on the stack"""
+
+    # define __len__ support for ISizedStack
+    when(ISizedStack.__len__, (list,))(list.__len__)
+
+Or assembled by combining functions from existing interfaces::
+
+    class Sizable(Interface):
+        __len__ = ISizedStack.__len__
+
+    # list now implements Sizable as well as ISizedStack, without
+    # making any new declarations!
+
+A class can be considered to "adapt to" an interface at a given
+point in time, if no method defined in the interface is guaranteed to
+raise a ``NoApplicableMethods`` error if invoked on an instance of
+that class at that point in time.
+
+In normal usage, however, it is "easier to ask forgiveness than
+permission".  That is, it is easier to simply use an interface on
+an object by adapting it to the interface (e.g. ``IStack(mylist)``)
+or invoking interface methods directly (e.g. ``IStack.push(mylist,
+42)``), than to try to figure out whether the object is adaptable to
+(or directly implements) the interface.
+
+
+Implementing an Interface in a Class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is possible to declare that a class directly implements an
+interface, using the ``declare_implementation()`` function::
+
+    from overloading import declare_implementation
+
+    class Stack(object):
+        def __init__(self):
+            self.data = []
+        def push(self, ob):
+            self.data.append(ob)
+        def pop(self):
+            return self.data.pop()
+
+    declare_implementation(IStack, Stack)
+
+The ``declare_implementation()`` call above is roughly equivalent to
+the following steps::
+
+    when(IStack.push, (Stack,object))(lambda self, ob: self.push(ob))
+    when(IStack.pop, (Stack,))(lambda self, ob: self.pop())
+
+That is, calling ``IStack.push()`` or ``IStack.pop()`` on an instance
+of any subclass of ``Stack``, will simply delegate to the actual
+``push()`` or ``pop()`` methods thereof.
+
+For the sake of efficiency, calling ``IStack(s)`` where ``s`` is an
+instance of ``Stack``, **may** return ``s`` rather than an ``IStack``
+adapter.  (Note that calling ``IStack(x)`` where ``x`` is already an
+``IStack`` adapter will always return ``x`` unchanged; this is an
+additional optimization allowed in cases where the adaptee is known
+to *directly* implement the interface, without adaptation.)
+
+For convenience, it may be useful to declare implementations in the
+class header, e.g.::
+
+    class Stack(metaclass=Implementer, implements=IStack):
+        ...
+
+Instead of calling ``declare_implementation()`` after the end of the
+suite.
+
+
+Interfaces as Type Specifiers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``Interface`` subclasses can be used as argument annotations to
+indicate what type of objects are acceptable to an overload, e.g.::
+
+    @overload
+    def traverse(g: IGraph, s: IStack):
+        g = IGraph(g)
+        s = IStack(s)
+        # etc....
+
+Note, however, that the actual arguments are *not* changed or adapted
+in any way by the mere use of an interface as a type specifier.  You
+must explicitly cast the objects to the appropriate interface, as
+shown above.
+
+Note, however, that other patterns of interface use are possible.
+For example, other interface implementations might not support
+adaptation, or might require that function arguments already be
+adapted to the specified interface.  So the exact semantics of using
+an interface as a type specifier are dependent on the interface
+objects you actually use.
+
+For the interface objects defined by this PEP, however, the semantics
+are as described above.  An interface I1 is considered "more specific"
+than another interface I2, if the set of descriptors in I1's
+inheritance hierarchy are a proper superset of the descriptors in I2's
+inheritance hierarchy.
+
+So, for example, ``ISizedStack`` is more specific than both
+``ISizable`` and ``ISizedStack``, irrespective of the inheritance
+relationships between these interfaces.  It is purely a question of
+what operations are included within those interfaces -- and the
+*names* of the operations are unimportant.
+
+Interfaces (at least the ones provided by ``overloading``) are always
+considered less-specific than concrete classes.  Other interface
+implementations can decide on their own specificity rules, both
+between interfaces and other interfaces, and between interfaces and
+classes.
+
+
+Non-Method Attributes in Interfaces
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``Interface`` implementation actually treats all attributes and
+methods (i.e. descriptors) in the same way: their ``__get__`` (and
+``__set__`` and ``__delete__``, if present) methods are called with
+the wrapped (adapted) object as "self".  For functions, this has the
+effect of creating a bound method linking the generic function to the
+wrapped object.
+
+For non-function attributes, it may be easiest to specify them using
+the ``property`` built-in, and the corresponding ``fget``, ``fset``,
+and ``fdel`` attributes::
+
+    class ILength(Interface):
+        @property
+        @abstract
+        def length(self):
+            """Read-only length attribute"""
+
+    # ILength(aList).length == list.__len__(aList) 
+    when(ILength.length.fget, (list,))(list.__len__)
+
+Alternatively, methods such as ``_get_foo()`` and ``_set_foo()``
+may be defined as part of the interface, and the property defined
+in terms of those methods, but this a bit more difficult for users
+to implement correctly when creating a class that directly implements
+the interface, as they would then need to match all the individual
+method names, not just the name of the property or attribute.
+
+
+Aspects
+-------
+
+The adaptation system provided assumes that adapters are "stateless",
+which is to say that adapters have no attributes or storage apart from
+those of the adapted object.  This follows the "typeclass/instance"
+model of Haskell, and the concept of "pure" (i.e., transitively
+composable) adapters.
+
+However, there are occasionally cases where, to provide a complete
+implementation of some interface, some sort of additional state is
+required.
+
+One possibility of course, would be to attach monkeypatched "private"
+attributes to the adaptee.  But this is subject to name collisions,
+and complicates the process of initialization.  It also doesn't work
+on objects that don't have a ``__dict__`` attribute.
+
+So the ``Aspect`` class is provided to make it easy to attach extra
+information to objects that either:
+
+1. have a ``__dict__`` attribute (so aspect instances can be stored
+   in it, keyed by aspect class),
+
+2. support weak referencing (so aspect instances can be managed using
+   a global but thread-safe weak-reference dictionary), or
+
+3. implement or can be adapt to the ``overloading.IAspectOwner``
+   interface (technically, #1 or #2 imply this)
+
+Subclassing ``Aspect`` creates an adapter class whose state is tied
+to the life of the adapted object.
+
+For example, suppose you would like to count all the times a certain
+method is called on instances of ``Target`` (a classic AOP example).
+You might do something like::
+
+    from overloading import Aspect
+
+    class Count(Aspect):
+        count = 0
+
+    @after(Target.some_method)
+    def count_after_call(self, *args, **kw):
+        Count(self).count += 1
+
+The above code will keep track of the number of times that
+``Target.some_method()`` is successfully called (i.e., it will not
+count errors).  Other code can then access the count using
+``Count(someTarget).count``.
+
+``Aspect`` instances can of course have ``__init__`` methods, to
+initialize any data structures.  They can use either ``__slots__``
+or dictionary-based attributes for storage.
+
+While this facility is rather primitive compared to a full-featured
+AOP tool like AspectJ, persons who wish to build pointcut libraries
+or other AspectJ-like features can certainly use ``Aspect`` objects
+and method-combination decorators as a base for more expressive AOP
+tools.
+
+XXX spec out full aspect API, including keys, N-to-1 aspects, manual
+    attach/detach/delete of aspect instances, and the ``IAspectOwner``
+    interface.
+    
+    
+Extension API
+=============
+
+TODO: explain how all of these work
+
+implies(o1, o2)
+
+declare_implementation(iface, class)
+
+predicate_signatures(ob)
+
+parse_rule(ruleset, body, predicate, actiontype, localdict, globaldict)
+
+combine_actions(a1, a2)
+
+rules_for(f)
+
+Rule objects
+
+ActionDef objects
+
+RuleSet objects
+
+Method objects
+
+MethodList objects
+
+IAspectOwner
+
+
+
+Implementation Notes
+====================
+
+Most of the functionality described in this PEP is already implemented
+in the in-development version of the PEAK-Rules framework.  In
+particular, the basic overloading and method combination framework
+(minus the ``@overload`` decorator) already exists there.  The
+implementation of all of these features in ``peak.rules.core`` is 656
+lines of Python at this writing.
+
+``peak.rules.core`` currently relies on the DecoratorTools and
+BytecodeAssembler modules, but both of these dependencies can be
+replaced, as DecoratorTools is used mainly for Python 2.3
+compatibility and to implement structure types (which can be done
+with named tuples in later versions of Python).  The use of
+BytecodeAssembler can be replaced using an "exec" or "compile"
+workaround, given a reasonable effort.  (It would be easier to do this
+if the ``func_closure`` attribute of function objects was writable.)
+
+The ``Interface`` class has been previously prototyped, but is not
+included in PEAK-Rules at the present time.
+
+The "implicit class rule" has previously been implemented in the
+RuleDispatch library.  However, it relies on the ``__metaclass__``
+hook that is currently eliminated in PEP 3115.
+
+I don't currently know how to make ``@overload`` play nicely with
+``classmethod`` and ``staticmethod`` in class bodies.  It's not really
+clear if it needs to, however.
+
+
+Copyright
+=========
+
+This document has been placed in the public domain.
+
+
+
+..
+   Local Variables:
+   mode: indented-text
+   indent-tabs-mode: nil
+   sentence-end-double-space: t
+   fill-column: 70
+   coding: utf-8
+   End:


More information about the Python-checkins mailing list