I accidently sent this directly to Richard. Sorry about that, folks...
---------- Forwarded message ---------- From: Jonathan Lang <[EMAIL PROTECTED]> Date: Aug 29, 2006 1:24 PM Subject: Re: Classes / roles as sets / subsets To: Richard Hainsworth <[EMAIL PROTECTED]> Richard Hainsworth wrote:
I find classes and roles, and multiple inheritance in general, difficult to understand. Larry Wall talked about subsets, so I have tried to analyse various situations using the idea of sets and subsets and Venn diagrams for demonstrating the relations between sets and subsets. The idea is that the 'space' encompassed by a set in the diagram (labelled as sets) is method functionality and attributes.
This may well miss the mark. Some of the most important differences between classes and roles come in when the sets of method namespaces don't match up with the sets of method implementations - that is, A and B both include a method m, but A's method m does something different than B's method m.
See diagram Case 1 (Class B is a subset of class A):
Note that in Case 1, there isn't much difference between classes and roles.
My understanding of inheritance in other languages is that Class A 'isa' Class B, and inherits all of the attributes and functionality of Class B, and extends functionality and attributes.
So far, so good. If you want A.m to do something different than B.m, A redefines m.
It is also possible for Class B to be ('isa') Class A, and ignore the extra functionality of A, presumably to create objects that are smaller than the more general class.
Not according to clean OO programming theory. If A isa B, then A will carry with it all of B's internal mechanisms, even the ones that it has closed off from public access. If it fails to do this, it isn't really a B. Even when A redefines how m works, it includes an implicit mechanism for getting at the original definition. As such, inheritance can only add to a class; it cannot remove or change anything in any substantive manner. The only point at which stuff gets removed is when the code optimizer comes along and trims the fat. Let's extend Case 1 by adding a Class C, which is a superset of Class A. C isa A, but A isa B; C can still access B.m, as well as A.m. Think of A, B, and C as being layers that get added on top of each other: A covers B, but doesn't actually replace it.
My suggested interpretation of roles: Class B is a role, and Class A is built from Class B, extending its functionality. A role without extra code becomes a class if it is instantiated.
The motto for role A is "anything you can do, I can do too." Note that when role A declares that it does role B, it makes a promise about _what_ it can do, but makes no promises about _how_ it will do it. In fact, roles don't even make a promise to supply any implementation information at all: B doesn't have to say a word about how m works - and for the purpose of composing roles, what it _does_ say about how m works should be taken not so much as a rule than as a guideline. As long as the composer has no reason _not_ to accept B's version of m, it will; but at the first hint of trouble, B's version of m will get jettisoned. In Case 1, the only way that role B's version of m will get jettisoned is if role A provides its own version. However, A is not a B; it is its own unique entity. If A invalidates B's suggestion for how m should work, then nothing that does A will implicitly have access to B's version of m. Unlike classes, roles can change from what they're composed from, not just add to them. However, because they don't guarantee to tell you how anything works, they can't be used to intantiate objects; that's what Classes are for.
See diagram case 2 (Class A and Class B intersect): Usual OO technique: Class B inherits the functionality of Class A, extends the functionality (in one 'direction') but then over-rides (anuls) some of the functionality of A (say, by redefining methods/attributes used in A). Question: Is this the sort of behaviour that is forbidden in some languages.
Very definitely, yes. Removing capabilities during inheritance is a no-no. If you wanted Classes A abd B to be part of the same inheritance tree, you'd have to define a common ancestor class that contains the intersection of A and B, and then each of A and B inherit from it. As described, though, A and B are completely unrelated by inheritance; each is a unique class which stands on its own. The fact that both contain a method m is a coincidence, nothing more. This _is_ a useful case to consider, though, in terms of what happens if you add a new class (C) which encompasses both A and B. Assuming that m is an element of both A and B, A.m doesn't neccessarily work the same way as B.m; C.m needs to decide which version to use. Traditionally, OO languages imnplement a system of seniority, where one of the classes that C inherits from is given precedence over the other - generally based on which one comes first in C's list of parent classes. The conflict between A.m and B.m thus gets resolved in favor of the senior parent. Of course, if C provides its own implementation for m, that takes precedence - but A.m and B.m will still be there, hiding under the surface.
Role-playing programming: For the sake of programming sanity / ease of debugging, both Classes A & B are built from a role that represents their intersection ( Class A U Class B), and then code is added in the definitions of the classes to extend the functionality - possibly using over-riding with same-name methods/attributes for different types.
AFAIK, removing methods is the one thing that roles can't do, either. With a role C that composes roles A and B, conflict resolution is handled differently: if C doesn't provide its own mechanism for m, and A and B provide potentially conflicting mechanisms for A.m and B.m respectively, C simply leaves m without an implementation. It's up to whatever class does C to decide how m works. And if that class wants to use A.m, it can't tunnel into C in order to get at it; it will have to explicitly do A as well, and then personally resolve any conflicts that come up between A and C.
See diagram case 3 (multiple subsets): This would require multiple inheritance. In perl6 (I think), Class A would be built from the 'roles' of classes B-D.
Perl6 handles both object-orientation (through inheritance) and role-playing (through composition). Multiple inheritence is an OO concept, and would work in much the same way as I described in Case 2. But because B, C, and D don't overlap, the multiple inheritance of Case 3 is comparatively trivial. When dealing with the RP model, Role A composes roles B, C, and D. Again, since none of them overlap with each other, there are no conflicts to resolve. Case 3 is actually closer to Case 1 in terms of simplicity.
But the question is what would 'ref' (or perl6 equivalent) return? Would it only define a match for (an object instantiated from) class A, or would it recognise the existence of Classes B-D in that same object? If so, how?
I _think_ that this answers your question: Under the OO model: if x is an object instantiated from A, it can be treated as if it were an object instantiated from B, C, or D as well. Under the RP model: if x is an object instantiated from a class that does A, it can be said that x does A; but it cannot be said that x does B, C, or D.
So suppose an object OA is an instance of class A and and object OB is an instance of class (role) B. Would object OA 'match' object OB in some form, after all the functionality of OB is contained within OA?
Yes, by treating OA is if it were a B.
But then, this matching might not be transitive, viz. OA ~~ OB might be true, but OB ~~ OA might be false because whilst OA contains the functionality of OB, OB does not contain the functionality of OA.
Correct. Again, this deals explicitly with the OO side of things. On the RP side of things, if OA is an instance of a class that does A and OB is an instance of a class that does B, neither OA ~~ OB nor OB ~~ OA will match.
See diagram case 4 (multiple intersecting sets):
This is "merely" a messier version of Case 2, with more than two intersecting sets. Everything that I said about Case 2 applies here as well. -- Jonathan "Dataweaver" Lang