Originally posted on use.perl, then I tried to figure out why I hadn't posted it here.
I was thinking about my live testing demo and what sort of module to create when I discarded the idea of creating a D&D style Character class. Just showing the basics seemed too simple, but my brain just wouldn't let Character.pm die. When the idea of a profession (sometimes known as a character class, but the term is too confusing in this context) started causing problems, I realized that I was looking at a classic failing of traditional OO type systems.
Imagine an abstract base class called Character that is subclassed by race (or species, if you prefer). Using a Perl6, we can imagine this:
class Elf is Character {
...
}
class Drow is Elf {
...
}
class Human is Character {
...
}
So far, all is well and good. But how do we create an Elven Thief? You don't want Thief to subclass from Elf because then each race gets a subclass for each profession and the number of classes quickly becomes ridiculous and you duplicate a lot of code. However, you can't have Elf subclass from Thief because not all elves are thieves unless you want to start toying with the idea of manipulating inheritance trees based upon an instance instead of a class (and all of the ridiculous problems that would bring.) Java interfaces are of no use because those are assigned at compile time and, in any event, don't provide the implementation.
So you have two unpleasant alternatives. A traditional one is to use delegation. The simplest method is to provide a "profession" slot that stores a Profession object:
class Thief is Profession {
...
}
my Elf $elf .= new(Thief.new);
But this is yet another problem. Now the elf has a reference to a Thief object but the abilities of the thief are tied to the abilities of the character instance, so the thief now has a dependency on an instance of a Character, so it probably stores the elf in a slot:
class Elf is Character {
has $:profession;
method new(Class $class: Profession $profession) {
$:profession = $profession;
# how the heck do I access the instance from within new()?
$profession.race($_); # certainly incorrect syntax
}
...
}
This, of course, means a circular reference which might have to be broken explicitly and, in any event, guarantees that classes are coupled more tightly than I like and I have to violate the Law of Demeter if I want to do $elf.profession.pick_pocket($mark);. Naturally this breaks down pretty quickly when the elf is a magician and gets his fingers broken (or worse.)
Ruby style mixins can help, but their ordering problems are well known and sometimes can cause difficulties that still force delegation.
Perl 6 roles seem to solve the problem (see Apocalypse 12). Roles that are assigned at runtime instead of compile-time are called "mixins" and they can apply to an instace of a class! Still, whatever they are called, they seem to simplify the problem. With mixins I can just do this:
$elf does Thief; # hmm ... that reads funnyAnd the Thief methods are automatically available to the $elf. Because inheritance is not involved, you don't have the ordering problems of multiple inheritance, nor do you have to worry about other classes picking up the undesirable trait (of being a thief.) Because interfaces are not used, you don't have the problem with duplicate code, but you do get the benefit of knowing the methods are implemented (and that required methods are available.) Because delegation is not involved, there are no "Law of Demeter" concerns, nor are the maintenance or performance penalties paid. All things considered, this seems to be a huge win. (mixins are apparently implemented in Perl 6 as anonymous classes attached to an instance but I don't know the implications of that.)
As it turns out, roles are still quite useful. Remember how some races, such as gnomes and elves, had infravision? Rather than reimplement:
class Elf is Character does Infravision {
...
}
class Gnome is Character does Infravision {
...
}
This is all so spectacularly useful that I'm surprised more people haven't wanted it, though I admit it's a new idea (and I've described it rather incompletely.)
And to wrap this up by bringing this back to reality, here's a classic OO example of bad inheritance. If you have an Employee class, how do you represent programmers and managers? One idea is to make two subclasses. This fails when your manager doubles as a programmer (as our's sometimes does.) Instead, you can use mixins:
$employee does Programmer does Manager;OO can be very difficult to get right and that's part of the reason we've had so many variations on OO over the decades. Roles, mixins, and other goodies are a fascinating experiment in giving programmers the tools to do things right.
Cheers,
Ovid
New address of my CGI Course.
$elf does Thief; # hmm ... that reads funny
Maybe that's because when we coudn't divorce the person from their actions, naming a (sub)class Thief made sense, but now we can have groups of actions that can be enacted by a range of People, maybe we should name such groups by the collective term for those actions:
my Elf $elf does Thieving;
And
my $indivual is Employee has HomeAddress, TelephoneNo, ParkingSpace does Program, Manage, FirstAid;
I wonder if P6 will allow lists to does, is, has and the like? It would certainly make the syntax more friendly.
Reads quite nicely, and seems quite intuative that if the employee gives up first aiding, removing that Role has little impact on the rest of his persona. Maybe:
$individual stops FirstAiding;
Then that dear old Elf from earlier sees the light and repents:
$elf stops Thieving;
Of course, it doesn't flow completely right. There would be no point in:
$indivual does Thieving;
Unless the compiler has the smarts to turn that into:
$individual isn't Employee;
:)
------
We are the carpenters and bricklayers of the Information Age.
Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose
I shouldn't have to say this, but any code, unless otherwise stated, is untested
I'm not sure what you mean by "That..."?
...
does Program, Manage, FirstAid;
was meant to imply the equivalent of
... does Program does Manage does FirstAid;
with $individual stops FirstAid; removing the latter Role when appropriate.
Unless your referring to the last bit about the compiler mapping $individual does Thieving; to $individual isn't Employee;, in which case, my attempt at humour failed completely.
------
We are the carpenters and bricklayers of the Information Age.
Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose
I shouldn't have to say this, but any code, unless otherwise stated, is untested
I wonder if P6 will allow lists to does, is, has and the like? It would certainly make the syntax more friendly.
Yes, but how would it be implemented? When you have a variadic operator, how do you avoid slurping up the rest of the line? If only one argument is allowed and the operator always return the object it's acting on, the current chaining idiom works.
Cheers,
Ovid
New address of my CGI Course.
I'll admit to having very little idea of what problems that would cause for parsing. I guess the list could be bracketed, but that wouldn't look so nice.
Another alternative is to have the list terminated by the presence of the next keyword which may be possible, but could also be a problem depending on how the parser is implemented.
Another is that the absence of a comma after the final argument is indicative of the end of the parameters for that operator, so the next token must be a new operator (or the end of statement.
SQL parsers seem happy to cope with comma seperated lists of args between keywords. How much difference there is between that and parsing Perl I'm not sure.
Well, it depends. If you are using roles, there are a couple of ways of resolving conflicts, but basically it works out as:
class Elf is Character does Thief does Scout {
method hide { .Scout::hide(@_) }
...
}
However, if you prefer, you can have your cake and eat it too.
method hide ($self: $action) {
# $self is required here because the topicalizer assigns
# $action to $_
given $action {
when Skulking { $self.Thief::hide($action) }
when Stalking { $self.Scount::hide($action) }
}
}
And that's nice because theoretically, if your elf knows various professions, she should know how the activities with those professions vary, just as one person might know how to drive a race car but wouldn't think of doing that with a dump truck, even though both activies are driving.
Unfortunately, when it comes to your specific example with mixins, I'm a little less clear about disambiguation. When you use a role at runtime on an instance, you get new anonymous classes that are related to the instance via inheritance. Thus, the following has the inheritance ordering problem again:
$elf does Thief does Sentry;In that example, $elf.hide calls Sentry.hide as Thief.hide is further up the inheritance tree. This seems to limit the utility of mixins, but I can't be sure. Further clarification would be nice.
Cheers,
Ovid
New address of my CGI Course.
Ultimately, it appears that the crux of the matter is defining what is a behavior vs. what is a trait.
A thief IS a Character who "implements/HAS" the behavior/interface of methods associated with "thieving". An Elf is a Character who IS a type of Character, who HAS certain additional traits.
It's all going to come down to whether you think of the character as an "elf type-of thief," or a "thief type-of elf." This will determine which is the child class by inheritance, and which is the child by "mix-in".
Just some thoughts,# ".?" means "call if the obj CAN the method" acc. to Apoc12 $stranger.?steal; $stranger.?shoot_arrows;and expect DWIMming (doing both things in both cases). That's the whole point of polymorphism, is it not? :)
Update: upgrade perl5 syntax to perl6 (Apoc12).
with the idea of manipulating inheritance trees based upon an instance instead of a class (and all of the ridiculous problems that would bring.)Cecil does this and seems to do it very nicely, although I've never used it, I just read their language specification.
I'm curious about this bit of code
$elf does Thief; # hmm ... that reads funnyis this attaching the methods at run time or at compile time? That is, do the methods stay attached to the object inside $elf or is it only treated as doing Thief in scope of this declaration? The second case would not be very useful and the first case basically amounts to "manipulating inheritance trees based upon an instance".
my $parser = new XML::Parser but Simple; # or my $mail = new MIME but Lite;:)
The second thing is that the trait Thief will not override methods that already exist in the inheritance tree of $elf. It will add the Thief methods as a fallback, should they be needed. And, it does it until you remove the Thief trait. A12 has a lot to say on the topic, which is good.
------
We are the carpenters and bricklayers of the Information Age.
Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose
I shouldn't have to say this, but any code, unless otherwise stated, is untested
The first case doesn't manipulate the inheritance trees based upon an instance. For one thing, the Perl6 model won't affect instance2 just because instance1 does Foo. That is probably the biggest difference.
That's exactly what I would call manipulating the inheritance tree based upon an instance. The only other meaning I can think of (and I think it's the meaning you thought I meant) is that twiddling instance1 has an effect on all other instances. This would be an extremely pointless feature and isn't what I was talking about.
So to quote Ovid again (the whole sentence this time)
However, you can't have Elf subclass from Thief because not all elves are thieves unless you want to start toying with the idea of manipulating inheritance trees based upon an instance instead of a class (and all of the ridiculous problems that would bring.)that reads to me like individual objects would have their own inheritance trees so some Elfs would subclass from Thief and some wouldn't. He calls this ridiculous but isn't that exactly what Perl 6 does?
I think the relevant part of A12 is:
Class Composition with RolesObjects have many kinds of relationships with other objects. One of the pitfalls of the early OO movement was to encourage people to model many relationships with inheritance that weren't really "isa" relationships. Various languages have sought to redress this deficiency in various ways, with varying degrees of success. With Perl 6 we'd like to back off a step and allow the user to define abstract relationships between classes without committing to a particular implementation.
More specifically, we buy the argument of the Traits paper that classes should not be used both to manage objects and to manage code reuse. It needs to be possible to separate those concerns. Since a lot of the code that people want to reuse is that which manages non-isa object relationships, that's what we should abstract out from classes.
That abstraction we are calling a role. Roles can encompass both interface and implementation of object relationships. A role without implementation degenerates to an interface. A role without interface degenerates to privately instantiated generics. But the typical role will provide both interface and at least a default implementation.
Unlike the Traits paper, we will allow state as part of our implementation. This is necessary if we are to abstract out the delegation decision. We feel that the decision to delegate rather than compose a sub-object is a matter of implementation, and therefore that decision should be encapsulated (or at least be allowed to be encapsulated) in a role. This allows you to refactor a problem by redefining one or more roles without having to doctor all the classes that make use of those roles. This is a great way to turn your huge, glorious "god object" into a cooperating set of objects that know how to delegate to each other.
As in the Traits paper, roles are composed at class construction time, and the class composer does some work to make sure the composed class is not unintentionally ambiguous. If two methods of the same name are composed into the same class, the ambiguity will be caught. The author of the class has various remedies for dealing with this situation, which we'll go into below.
From the standpoint of the typical user, a role just looks like a "smart" include of a "partial class". They're smart in that roles have to be well behaved in certain respects, but most of the time the naive user can ignore the power of the abstraction.
The enboldened (my highlighting) part in the last paragraph forms the mental model that I am using.
In effect, the "smart include" means that there is no inheritance involved--although another part of a12 goes on to say that it maybe implemented using a form of MI at the Parrot level--the Role's methods actually become a part of the Class(es) to which they are applied when used in the compile-time form. Where the does is applied to the class itself, and applies to all instances of that class.
In the runtime form, where the Role is applied to an instance of some class, only that instance gains the new methods. In implementation, a new anonymous class is created that is the composition of that instance' class and the Role.
I take this to mean that if a Role is applied to an instance of class Dog at runtime, and the the Class Dog was subsequently modified (through introspection for example), the instance that had the Role applied at runtime, would continue to be based on the original version, not the modified version of Dog.
In the end, I doubt that these details will have any great significance in use. I can imagine there being a Storable Role that know how to copy an instance' attributes to and from disk without needing to know much of anything about the Class of instance to which it is applied. If you have have (or get from somewhere) a class that does what you need, but you need to be able to save and restore the instances, you apply the Storable Role to it. At compile time of all instances need it. At runtime if only some instances need it.
The Storable Role would be defined as an interface (or perhaps with a minimal implementation). Then you (your site) provide an implementation of the defined interface that meets your requirements: Flat files, Berkeley, RDBMS of some flavour, OODB. The interface might be defined to have two methods: .freeze :id; and .thaw :id;. Switching DB vendors becomes a case of installing their implementation of the Storable Role, and everything else continues as is.
Or maybe that would be better implemented as a Trait? There is so much in A12 it's hard to put your finger on what wil become best practice.
I believe it is inevitable that best practice will not be understood until a couple years after Perl6 is out.
After all, this was no different after the transition from Perl4 to Perl5. I'm sure that implementing Exporter using inheritance seemed like an absolutely great idea at the time, f.ex. — and that's just the most obvious and visible example of goofy stuff in the core modules. They all indicate how the top ranks of Perl4 hackers tried to adapt to the newfangled stuff in Perl5.
Doubtless, we will produce just as much nonsensical gibberish in Perl6 before we start getting an unencumbered feel for the new language.
Makeshifts last the longest.
Well, I think I goofed on this one. From reading A12, it appears that anonymous classes are built and bound to objects that use mixins. I missed that point and now it seems like we're going to have inheritance problems again. When I posted a question about this to the Perl 6 language list, Larry Wall responded:
That's just a problem with run-time mixins in general if you can't know in advance that you're going to want both roles. That's why we encourage as much of that to be done at compile time as possible.
This does seem like it will cause more design issues, but Larry and TheDamian both had ways around this. They suggested strategies such as creating anonymous classes or anonymous roles that could disambiguate methods as needed. I guess that makes sense as magician thieves would want the magic users spell-casting ability but the thief's fighting ability and those seem like rules that might have to be decided at compile time.
You can read the Perl 6 language discussion here.
Cheers,
Ovid
New address of my CGI Course.
perlmonks.org content © perlmonks.org and Anonymous Monk, Aristotle, BrowserUk, dragonchild, fergal, kappa, Ovid, Velaki
prlmnks.org © 2006 edmund von der burg (eccles & toad)
v 0.03