[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