[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