[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