[Python-checkins] r55308 - peps/trunk/pep-0000.txt peps/trunk/pep-3133.txt

collin.winter python-checkins at python.org
Mon May 14 07:36:05 CEST 2007


Author: collin.winter
Date: Mon May 14 07:36:02 2007
New Revision: 55308

Added:
   peps/trunk/pep-3133.txt
Modified:
   peps/trunk/pep-0000.txt
Log:
Add PEP 3133, Introducing Roles.

Modified: peps/trunk/pep-0000.txt
==============================================================================
--- peps/trunk/pep-0000.txt	(original)
+++ peps/trunk/pep-0000.txt	Mon May 14 07:36:02 2007
@@ -122,6 +122,7 @@
  S  3119  Introducing Abstract Base Classes            GvR, Talin
  S  3124  Overloading, Generic Functions, Interfaces   Eby
  S  3131  Supporting Non-ASCII Identifiers             von Löwis
+ S  3133  Introducing Roles                            Winter
  S  3141  A Type Hierarchy for Numbers                 Yasskin
 
  Finished PEPs (done, implemented in Subversion)
@@ -502,6 +503,7 @@
  SR 3130  Access to Current Module/Class/Function      Jewett
  S  3131  Supporting Non-ASCII Identifiers             von Löwis
  SF 3132  Extended Iterable Unpacking                  Brandl
+ S  3133  Introducing Roles                            Winter
  S  3141  A Type Hierarchy for Numbers                 Yasskin
 
 

Added: peps/trunk/pep-3133.txt
==============================================================================
--- (empty file)
+++ peps/trunk/pep-3133.txt	Mon May 14 07:36:02 2007
@@ -0,0 +1,556 @@
+PEP: 3133
+Title: Introducing Roles
+Version: $Revision$
+Last-Modified: $Date$
+Author: Collin Winter <collinw at gmail.com>
+Status: Draft
+Type: Standards Track
+Requires: 3115, 3129
+Content-Type: text/x-rst
+Created: 1-May-2007
+Python-Version: 3.0
+Post-History: 13-May-2007
+
+
+Abstract
+========
+
+Python's existing object model organizes objects according to their
+implementation.  It is often desirable -- especially in
+duck typing-based language like Python -- to organize objects by
+the part they play in a larger system (their intent), rather than by
+how they fulfill that part (their implementation).  This PEP
+introduces the concept of roles, a mechanism for organizing
+objects according to their intent rather than their implementation.
+
+
+Rationale
+=========
+
+In the beginning were objects.  They allowed programmers to marry
+function and state, and to increase code reusability through concepts
+like polymorphism and inheritance, and lo, it was good.  There came
+a time, however, when inheritance and polymorphism weren't enough.
+With the invention of both dogs and trees, we were no longer able to
+be content with knowing merely, "Does it understand 'bark'?"
+We now needed to know what a given object thought that "bark" meant.
+
+One solution, the one detailed here, is that of roles, a mechanism
+orthogonal and complementary to the traditional class/instance system.
+Whereas classes concern themselves with state and implementation, the
+roles mechanism deals exclusively with the behaviours embodied in a
+given class.
+
+This system was originally called "traits" and implemented for Squeak
+Smalltalk [#traits-paper]_.  It has since been adapted for use in
+Perl 6 [#perl6-s12]_ where it is called "roles", and it is primarily
+from there that the concept is now being interpreted for Python 3.
+Python 3 will preserve the name "roles".
+
+In a nutshell: roles tell you *what* an object does, classes tell you
+*how* an object does it.
+
+In this PEP, I will outline a system for Python 3 that will make it
+possible to easily determine whether a given object's understanding
+of "bark" is tree-like or dog-like.  (There might also be more
+serious examples.)
+
+
+A Note on Syntax
+----------------
+
+A syntax proposals in this PEP are tentative and should be
+considered to be strawmen.  The necessary bits that this PEP depends
+on -- namely PEP 3115's class definition syntax and PEP 3129's class
+decorators -- are still being formalized and may change.  Function
+names will, of course, be subject to lengthy bikeshedding debates.
+
+
+Performing Your Role
+====================
+
+Static Role Assignment
+----------------------
+
+Let's start out by defining ``Tree`` and ``Dog`` classes ::
+
+  class Tree(Vegetable):
+    
+    def bark(self):
+      return self.is_rough()
+
+
+  class Dog(Animal):
+    
+    def bark(self):
+      return self.goes_ruff()
+
+While both implement a ``bark()`` method with the same signature,
+they do wildly different things.  We need some way of differentiating
+what we're expecting. Relying on inheritance and a simple
+``isinstance()`` test will limit code reuse and/or force any dog-like
+classes to inherit from ``Dog``, whether or not that makes sense.
+Let's see if roles can help. ::
+    
+  @perform_role(Doglike)
+  class Dog(Animal):
+    ...
+
+  @perform_role(Treelike)
+  class Tree(Vegetable):
+    ...
+    
+  @perform_role(SitThere)
+  class Rock(Mineral):
+    ...
+    
+We use class decorators from PEP 3129 to associate a particular role
+or roles with a class.  Client code can now verify that an incoming
+object performs the ``Doglike`` role, allowing it to handle ``Wolf``,
+``LaughingHyena`` and ``Aibo`` [#aibo]_ instances, too.
+
+Roles can be composed via normal inheritance: ::
+
+  @perform_role(Guard, MummysLittleDarling)
+  class GermanShepherd(Dog):
+    
+    def guard(self, the_precious):
+      while True:
+        if intruder_near(the_precious):
+          self.growl()
+          
+    def get_petted(self):
+      self.swallow_pride()
+
+Here, ``GermanShepherd`` instances perform three roles: ``Guard`` and
+``MummysLittleDarling`` are applied directly, whereas ``Doglike``
+is inherited from ``Dog``.
+
+
+Assigning Roles at Runtime
+--------------------------
+
+Roles can be assigned at runtime, too, by unpacking the syntactic
+sugar provided by decorators.
+
+Say we import a ``Robot`` class from another module, and since we
+know that ``Robot`` already implements our ``Guard`` interface,
+we'd like it to play nicely with guard-related code, too. ::
+
+  >>> perform(Guard)(Robot)
+
+This takes effect immediately and impacts all instances of ``Robot``.
+
+
+Asking Questions About Roles
+----------------------------
+
+Just because we've told our robot army that they're guards, we'd
+like to check in on them occasionally and make sure they're still at
+their task. ::
+
+  >>> performs(our_robot, Guard)
+  True
+   
+What about that one robot over there? ::
+
+  >>> performs(that_robot_over_there, Guard)
+  True
+
+The ``performs()`` function is used to ask if a given object
+fulfills a given role.  It cannot be used, however, to ask a
+class if its instances fulfill a role: ::
+
+  >>> performs(Robot, Guard)
+  False
+ 
+This is because the ``Robot`` class is not interchangeable
+with a ``Robot`` instance.
+
+
+Defining New Roles
+==================
+
+Empty Roles
+-----------
+
+Roles are defined like a normal class, but use the ``Role``
+metaclass. ::
+
+  class Doglike(metaclass=Role):
+    ...
+
+Metaclasses are used to indicate that ``Doglike`` is a ``Role`` in
+the same way 5 is an ``int`` and ``tuple`` is a ``type``.
+
+
+Composing Roles via Inheritance
+-------------------------------
+
+Roles may inherit from other roles; this has the effect of composing
+them.  Here, instances of ``Dog`` will perform both the
+``Doglike`` and ``FourLegs`` roles. ::
+
+  class FourLegs(metaclass=Role):
+    pass
+
+  class Doglike(FourLegs, Carnivor):
+    pass
+ 
+  @perform_role(Doglike)  
+  class Dog(Mammal):
+    pass
+
+
+Requiring Concrete Methods
+--------------------------
+
+So far we've only defined empty roles -- not very useful things.
+Let's now require that all classes that claim to fulfill the
+``Doglike`` role define a ``bark()`` method: ::
+
+  class Doglike(FourLegs):
+
+    def bark(self):
+      pass
+      
+No decorators are required to flag the method as "abstract", and the
+method will never be called, meaning whatever code it contains (if any)
+is irrelevant.  Roles provide *only* abstract methods; concrete
+default implementations are left to other, better-suited mechanisms
+like mixins.
+
+Once you have defined a role, and a class has claimed to perform that
+role, it is essential that that claim be verified.  Here, the
+programmer has misspelled one of the methods required by the role. ::
+
+  @perform_role(FourLegs)
+  class Horse(Mammal):
+
+    def run_like_teh_wind(self)
+      ...
+      
+This will cause the role system to raise an exception, complaining
+that you're missing a ``run_like_the_wind()`` method.  The role
+system carries out these checks as soon as a class is flagged as
+performing a given role.
+
+Concrete methods are required to match exactly the signature demanded
+by the role.  Here, we've attempted to fulfill our role by defining a
+concrete version of ``bark()``, but we've missed the mark a bit. ::
+
+  @perform_role(Doglike)
+  class Coyote(Mammal):
+    
+    def bark(self, target=moon):
+      pass
+
+This method's signature doesn't match exactly with what the
+``Doglike`` role was expecting, so the role system will throw a bit
+of a tantrum.
+
+
+Mechanism
+=========
+
+The following are strawman proposals for how roles might be expressed
+in Python.  The examples here are phrased in a way that the roles
+mechanism may be implemented without changing the Python interpreter.
+(Examples adapted from an article on Perl 6 roles by Curtis Poe
+[#roles-examples]_.)
+
+1. Static class role assignment ::
+
+     @perform_role(Thieving)
+     class Elf(Character):
+       ...
+       
+   ``perform_role()`` accepts multiple arguments, such that this is
+   also legal: ::
+   
+     @perform_role(Thieving, Spying, Archer)
+     class Elf(Character):
+       ...
+       
+   The ``Elf`` class now performs both the ``Thieving``, ``Spying``,
+   and ``Archer`` roles.   
+
+2. Querying instances ::
+
+     if performs(my_elf, Thieving):
+       ...
+       
+   The second argument to ``performs()`` may also be anything with a
+   ``__contains__()`` method, meaning the following is legal: ::
+   
+     if performs(my_elf, set([Thieving, Spying, BoyScout])):
+       ...
+       
+   Like ``isinstance()``, the object needs only to perform a single
+   role out of the set in order for the expression to be true.
+
+
+Relationship to Abstract Base Classes
+=====================================
+
+Early drafts of this PEP [#proposal]_ envisioned roles as competing
+with the abstract base classes proposed in PEP 3119.  After further
+discussion and deliberation, a compromise and a delegation of
+responsibilities and use-cases has been worked out as follows:
+
+* Roles provide a way of indicating a object's semantics and abstract
+  capabilities.  A role may define abstract methods, but only as a
+  way of delineating an interface through which a particular set of
+  semantics are accessed.  An ``Ordering`` role might require that
+  some set of ordering operators  be defined. ::
+ 
+    class Ordering(metaclass=Role):
+      def __ge__(self, other):
+        pass
+    
+      def __le__(self, other):
+        pass
+        
+      def __ne__(self, other):
+        pass
+      
+      # ...and so on
+      
+  In this way, we're able to indicate an object's role or function
+  within a larger system without constraining or concerning ourselves
+  with a particular implementation.
+      
+* Abstract base classes, by contrast, are a way of reusing common,
+  discrete units of implementation.  For example, one might define an
+  ``OrderingMixin`` that implements several ordering operators in
+  terms of other operators. ::
+ 
+    class OrderingMixin:
+      def __ge__(self, other):
+        return self > other or self == other
+    
+      def __le__(self, other):
+        return self < other or self == other
+    
+      def __ne__(self, other):
+        return not self == other
+      
+      # ...and so on
+    
+  Using this abstract base class - more properly, a concrete
+  mixin - allows a programmer to define a limited set of operators
+  and let the mixin in effect "derive" the others.
+
+By combining these two orthogonal systems, we're able to both
+a) provide functionality, and b) alert consumer systems to the
+presence and availability of this functionality.  For example,
+since the ``OrderingMixin`` class above satisfies the interface
+and semantics expressed in the ``Ordering`` role, we say the mixin
+performs the role: ::
+
+  @perform_role(Ordering)
+  class OrderingMixin:
+    def __ge__(self, other):
+      return self > other or self == other
+
+    def __le__(self, other):
+      return self < other or self == other
+
+    def __ne__(self, other):
+      return not self == other
+
+    # ...and so on
+
+Now, any class that uses the mixin will automatically -- that is,
+without further programmer effort -- be tagged as performing the
+``Ordering`` role.
+
+The separation of concerns into two distinct, orthogonal systems
+is desirable because it allows us to use each one separately.
+Take, for example, a third-party package providing a
+``RecursiveHash`` role that indicates a container takes its
+contents into account when determining its hash value.  Since
+Python's built-in ``tuple`` and ``frozenset`` classes follow this
+semantic, the ``RecursiveHash`` role can be applied to them. ::
+
+  >>> perform_role(RecursiveHash)(tuple)
+  >>> perform_role(RecursiveHash)(frozenset)
+
+Now, any code that consumes ``RecursiveHash`` objects will now be
+able to consume tuples and frozensets.
+
+
+Open Issues
+===========
+
+Allowing Instances to Perform Different Roles Than Their Class
+--------------------------------------------------------------
+
+Perl 6 allows instances to perform different roles than their class.
+These changes are local to the single instance and do not affect
+other instances of the class.  For example: ::
+
+  my_elf = Elf()
+  my_elf.goes_on_quest()
+  my_elf.becomes_evil()
+  now_performs(my_elf, Thieving) # Only this one elf is a thief
+  my_elf.steals(["purses", "candy", "kisses"])
+
+In Perl 6, this is done by creating an anonymous class that
+inherits from the instance's original parent and performs the
+additional role(s).  This is possible in Python 3, though whether it
+is desirable is still is another matter.
+
+Inclusion of this feature would, of course, make it much easier to
+express the works of Charles Dickens in Python: ::
+
+  >>> from literature import role, BildungsRoman
+  >>> from dickens import Urchin, Gentleman
+  >>>
+  >>> with BildungsRoman() as OliverTwist:
+  ...   mr_brownlow = Gentleman()
+  ...   oliver, artful_dodger = Urchin(), Urchin()
+  ...   now_performs(artful_dodger, [role.Thief, role.Scoundrel])
+  ...
+  ...   oliver.has_adventures_with(ArtfulDodger)
+  ...   mr_brownlow.adopt_orphan(oliver)
+  ...   now_performs(oliver, role.RichWard)
+
+
+Requiring Attributes
+--------------------
+
+Neal Norwitz has requested the ability to make assertions about
+the presence of attributes using the same mechanism used to require
+methods.  Since roles take effect at class definition-time, and
+since the vast majority of attributes are defined at runtime by a
+class's ``__init__()`` method, there doesn't seem to be a good way
+to check for attributes at the same time as methods.
+
+It may still be desirable to include non-enforced attributes in the
+role definition, if only for documentation purposes.
+
+
+Roles of Roles
+--------------
+
+Under the proposed semantics, it is possible for roles to
+have roles of their own. ::
+
+  @perform_role(Y)
+  class X(metaclass=Role):
+    ...
+
+While this is possible, it is meaningless, since roles
+are generally not instantiated.  There has been some
+off-line discussion about giving meaning to this expression, but so
+far no good ideas have emerged.
+
+
+class_performs()
+----------------
+
+It is currently not possible to ask a class if its instances perform
+a given role.  It may be desirable to provide an analogue to
+``performs()`` such that ::
+
+  >>> isinstance(my_dwarf, Dwarf)
+  True
+  >>> performs(my_dwarf, Surly)
+  True
+  >>> performs(Dwarf, Surly)
+  False
+  >>> class_performs(Dwarf, Surly)
+  True
+ 
+
+Prettier Dynamic Role Assignment
+--------------------------------
+
+An early draft of this PEP included a separate mechanism for
+dynamically assigning a role to a class.  This was spelled ::
+
+  >>> now_perform(Dwarf, GoldMiner)
+ 
+This same functionality already exists by unpacking the syntactic
+sugar provided by decorators: ::
+
+  >>> perform_role(GoldMiner)(Dwarf)
+ 
+At issue is whether dynamic role assignment is sufficiently important
+to warrant a dedicated spelling.
+
+
+Syntax Support
+--------------
+
+Though the phrasings laid out in this PEP are designed so that the
+roles system could be shipped as a stand-alone package, it may be
+desirable to add special syntax for defining, assigning and
+querying roles.  One example might be a role keyword, which would
+translate ::
+
+  class MyRole(metaclass=Role):
+    ...
+    
+into ::
+
+  role MyRole:
+    ...
+    
+Assigning a role could take advantage of the class definition
+arguments proposed in PEP 3115: ::
+
+  class MyClass(performs=MyRole):
+    ...
+
+
+Implementation
+==============
+
+A reference implementation is forthcoming.
+
+
+Acknowledgements
+================
+
+Thanks to Jeffery Yasskin, Talin and Guido van Rossum for several
+hours of in-person discussion to iron out the differences, overlap
+and finer points of roles and abstract base classes.
+
+
+References
+==========
+
+.. [#aibo]
+   http://en.wikipedia.org/wiki/AIBO
+
+.. [#roles-examples]
+   http://www.perlmonks.org/?node_id=384858
+   
+.. [#perl6-s12]
+   http://dev.perl.org/perl6/doc/design/syn/S12.html
+   
+.. [#traits-paper]
+   http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf
+   
+.. [#proposal]
+   http://mail.python.org/pipermail/python-3000/2007-April/007026.html
+
+
+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