[Python-3000] PEP 3133: Introducing Roles

Talin talin at acm.org
Mon May 14 09:48:23 CEST 2007


Collin Winter wrote:
> PEP: 3133

[snip]

I'll probably have quite a few comments on this over the next few days. 
First let me start off by saying I like the general approach of your PEP.

Let me kick off the bikeshed part of the discussion by saying that the 
"role/performs" terminology is not my favorite - I kind of like the 
terminology that was introduced by Jeff Shell in an earlier message, 
specifically the terms "specifies", "provides" and "implements":

   * An interface *specifies* a set of methods.
   * An object can *provide* the services that are specified by an 
interface.
   * A class can *implement* the services that are specified by an 
interface.

In other words, the difference between 'provides' and 'implements' is 
that one is asking about the instance and another is asking about the class.

Thus, you can ask an object if it provides() an interface; You can also 
ask a class if it implements() an interface.


Another question that I'd like to ask is: Your PEP describes a mechanism 
for defining roles and testing for them. What it doesn't define is what 
roles will be defined in the standard library, and specifically what 
roles will be defined for the built-in classes.


The third issue I want to raise is how the roles system interacts with 
PJE's generic functions PEP. Let me give some background:

In the most general terms, a method of a generic function is a function 
with a set of constraints on the arguments. These constraints can be 
types, but they don't have to be. Depending on the actual calling 
arguments, the dispatcher will attempt to find the method whose 
constraints most closely match the calling arguments.

Clearly, in a system in which there are both roles and generics, we 
would want to create overloads in which the constraints can be role 
tests rather than type tests.

So for example, if Guard is a role, we want to be able to dispatch on it:

    @overload
    def idle( actor: Guard ):
       ...

We would also like to be able to define methods that contain both 
type-tests and role-tests:

    @overload
    def watch( actor: Guard, treasure: list ):
       ...

In order for this to work, the dispatcher will need to know that the 
first argument requires a role-test ("performs" or whatever), while the 
second argument requires a type-test. I would like to see some more 
detail on how this would work.

However, its even more complicated than that. Generic function 
dispatchers can be made to work efficiently if there is a way to compare 
constraints with each other. Specifically, what you need to know is 
this: given any two tests, are those tests completely disjoint, is one 
test a subset of the other, or neither?

For example, suppose we have the following overloads:

    class MyList( list ):
       ...

    @overload
    def watch( a: list )
       ...

    @overload
    def watch( a: tuple )
       ...

    @overload
    def watch( a: MyList )

The most efficient dispatch algorithm for this particular set of 
overloads will first test to see if the argument is a list; If not, it 
will test to see if it's a tuple, otherwise it will test to see if it's 
a MyList.

In other words, even though there are three possible tests, we only need 
to perform two of them at most, because if it is a list, then it can't 
possibly be a tuple, and if it's not a list then it can't possibly be a 
MyList. As you add more overloads and more tests, this kind of pruning 
becomes important, and there are some wonderful algorithms for figuring 
this all out.

Now consider, however, the following situation, where you have a role, a 
class which implements that role, and a subclass:

    class Worker( Role ):
       ...

    @perform_role( Worker )
    class Robot:
       ...

    class ShinyRobot( Robot ):
       ...

Now, suppose we have a number of overloads:

    @overload
    def work( actor: Worker ):
       ...

    @overload
    def work( actor: Robot ):
       ...

    @overload
    def work( actor: ShinyRobot ):
       ...

In this case, the dispatching on the first argument we are sometimes 
doing type tests, and sometimes doing role tests.

Furthermore, we have an interaction between roles and types: The 
ShinyRobot test (a type test) can never succeed unless the role test 
(Worker) also succeeds. For purposes of dispatching efficiency, we want 
to be able to allow the dispatcher to know that the "ShinyRobot" is a 
subset of "Worker", even though the two tests are different kinds of tests.

Thus, the generic function dispatcher will need to be able to take two 
tests, which might both be type tests, or both role tests, or one of 
each - and compare them to see if one is a subset of the other, or if 
they overlap at all.


-- Talin



More information about the Python-3000 mailing list