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

phillip.eby python-checkins at python.org
Tue Jul 24 23:52:25 CEST 2007


Author: phillip.eby
Date: Tue Jul 24 23:52:24 2007
New Revision: 56534

Modified:
   peps/trunk/pep-3124.txt
Log:
Misc. fixes/clarifications, add section with rationale for why universal
overloading doesn't automatially lead to chaos and anarchy.


Modified: peps/trunk/pep-3124.txt
==============================================================================
--- peps/trunk/pep-3124.txt	(original)
+++ peps/trunk/pep-3124.txt	Tue Jul 24 23:52:24 2007
@@ -25,7 +25,8 @@
 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.
+CLOS and AspectJ), and simple forms of aspect-oriented programming
+(AOP).
 
 The proposed API is also open to extension; that is, it will be
 possible for library developers to implement their own specialized
@@ -384,7 +385,7 @@
 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.
+most-specific "before" method.
 
 Unlike "before" and "after" methods, however, "Around" methods *are*
 responsible for calling their ``__proceed__`` argument, in order to
@@ -615,10 +616,32 @@
 ``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.)
+
+Abstract and Concrete Methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Note, by the way, that 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.
+
+Also note that interface methods need not be abstract; one could, for
+example, write an interface like this::
+
+    class IWriteMapping(Interface):
+        @abstract
+        def __setitem__(self, key, value):
+            """This has to be implemented"""
+
+        def update(self, other:IReadMapping):
+            for k, v in IReadMapping(other).items():
+                self[k] = v
+
+As long as ``__setitem__`` is defined for some type, the above
+interface will provide a usable ``update()`` implementation.  However,
+if some specific type (or pair of types) has a more efficient way of
+handling ``update()`` operations, an appropriate overload can still
+be registered for use in that case.
 
 
 Subclassing and Re-assembly
@@ -765,10 +788,11 @@
 
     # 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
+in terms of those methods, but this is 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.
@@ -777,9 +801,9 @@
 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"
+The adaptation system described above assumes that adapters are "stateless",
+which is to say that adapters have no attributes or state apart from
+that of the adapted object.  This follows the "typeclass/instance"
 model of Haskell, and the concept of "pure" (i.e., transitively
 composable) adapters.
 
@@ -789,8 +813,10 @@
 
 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.
+and complicates the process of initialization (since any code using
+these attributes has to check for their existence and initialize them
+if necessary).  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:
@@ -802,7 +828,7 @@
    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)
+   interface (technically, #1 or #2 imply this).
 
 Subclassing ``Aspect`` creates an adapter class whose state is tied
 to the life of the adapted object.
@@ -817,13 +843,14 @@
         count = 0
 
     @after(Target.some_method)
-    def count_after_call(self, *args, **kw):
+    def count_after_call(self:Target, *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``.
+``Target.some_method()`` is successfully called on an instance of
+``Target`` (i.e., it will not count errors unless they occur in a
+more-specific "after" method).  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__``
@@ -832,8 +859,8 @@
 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.
+and method-combination decorators as a base for building 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``
@@ -870,6 +897,98 @@
 IAspectOwner
 
 
+Overloading Usage Patterns
+==========================
+
+In discussion on the Python-3000 list, the proposed feature of allowing
+arbitrary functions to be overloaded has been somewhat controversial,
+with some people expressing concern that this would make programs more
+difficult to understand.
+
+The general thrust of this argument is that one cannot rely on what a
+function does, if it can be changed from anywhere in the program at any
+time.  Even though in principle this can already happen through
+monkeypatching or code substitution, it is considered poor practice to
+do so.
+
+However, providing support for overloading any function (or so the
+argument goes), is implicitly blessing such changes as being an
+acceptable practice.
+
+This argument appears to make sense in theory, but it is almost entirely
+mooted in practice for two reasons.
+
+First, people are generally not perverse, defining a function to do one
+thing in one place, and then summarily defining it to do the opposite
+somewhere else!  The principal reasons to extend the behavior of a
+function that has *not* been specifically made generic are to:
+
+* Add special cases not contemplated by the original function's author,
+  such as support for additional types.
+
+* Be notified of an action in order to cause some related operation to
+  be performed, either before the original operation is performed,
+  after it, or both.  This can include general-purpose operations like
+  adding logging, timing, or tracing, as well as application-specific
+  behavior.
+
+None of these reasons for adding overloads imply any change to the
+intended default or overall behavior of the existing function, however.
+Just as a base class method may be overridden by a subclass for these
+same two reasons, so too may a function be overloaded to provide for
+such enhancements.
+
+In other words, universal overloading does not equal *arbitrary*
+overloading, in the sense that we need not expect people to randomly
+redefine the behavior of existing functions in illogical or
+unpredictable ways.  If they did so, it would be no less of a bad
+practice than any other way of writing illogical or unpredictable code!
+
+However, to distinguish bad practice from good, it is perhaps necessary
+to clarify further what good practice for defining overloads *is*.  And
+that brings us to the second reason why generic functions do not
+necessarily make programs harder to understand: overloading patterns in
+actual programs tend to follow very predictable patterns.  (Both in
+Python and in languages that have no *non*-generic functions.)
+
+If a module is defining a new generic operation, it will usually also
+define any required overloads for existing types in the same place.
+Likewise, if a module is defining a new type, then it will usually
+define overloads there for any generic functions that it knows or cares
+about.
+
+As a result, the vast majority of overloads can be found adjacent to
+either the function being overloaded, or to a newly-defined type for
+which the overload is adding support.  Thus, overloads are highly-
+discoverable in the common case, as you are either looking at the
+function or the type, or both.
+
+It is only in rather infrequent cases that one will have overloads in a
+module that contains neither the function nor the type(s) for which the
+overload is added.  This would be the case if, say, a third-party
+created a bridge of support between one library's types and another
+library's generic function(s).  In such a case, however, best practice
+suggests prominently advertising this, especially by way of the module
+name.
+
+For example, PyProtocols defines such bridge support for working with
+Zope interfaces and legacy Twisted interfaces, using modules called
+``protocols.twisted_support`` and ``protocols.zope_support``.  (These
+bridges are done with interface adapters, rather than generic functions,
+but the basic principle is the same.)
+
+In short, understanding programs in the presence of universal
+overloading need not be any more difficult, given that the vast majority
+of overloads will either be adjacent to a function, or the definition of
+a type that is passed to that function.
+
+And, in the absence of incompetence or deliberate intention to be
+obscure, the few overloads that are not adjacent to the relevant type(s)
+or function(s), will generally not need to be understood or known about
+outside the scope where those overloads are defined.  (Except in the
+"support modules" case, where best practice suggests naming them
+accordingly.)
+
 
 Implementation Notes
 ====================


More information about the Python-checkins mailing list