It would be useful (or at least an interesting experiment) to add to a framework I help maintain the ability to compose objects from multiple classes at runtime.

Having done the requisite digging through CPAN and thread-hunting here, I'm not sure that what I have in mind exactly has been Modularized or Discussed (which should simply be read as my having succumbed to the programmer's siren song: roll your own, roll your own).

A 10-line function that implements per-object mixins follows, with some thoughts, questions, and example usage.

Several threads here discuss multiple inheritance, decorators, mixins, and prototype-style OO programming. For example:

And several nifty CPAN modules are relevant, for example:

I dig the Self approach, and reflective programming, but I really don't want a full-blown system, here. I just want, at run time, to wham together the methods defined in several different classes. Pretty much what Class::Object does, but I'm not even willing to use a new syntax to declare methods. Pretty much what mixin does, but at run time, and maybe even a bit simpler.

Dave Rolsky's Class::ClassDecorator is probably the closest thing to what I have in mind. And I may end up using it. But conceptually, decorators aren't what I want (more about which below). And, because of what Dave is trying to do, he has to run every method call through his AUTOLOADer. It's silly to worry about that (premature optimization of the worse kind), but I mention it because, well, that's how I am.

Mostly -- and perhaps this is a bit of mis-prioritization --I want these mixed objects to look as much as possible like all the other objects in the system. Here's a bit of working (at least to a first approximation) code:

#!/usr/bin/perl -w use strict; package Object; sub new { return bless {}, shift; } sub speak { print shift() . " says hello\n"; }; package mixin_simple; sub yell { print shift() . " says HELLO\n"; } package mixin_shadow; sub speak { print shift() . " whispers hello\n"; }; package main; sub mix { my ( $object, @classes ) = @_; push @classes, ref $object; my $package_name = join ( '_', @classes ); my $package_list = join ( ' ', @classes ); my $declaration = "package $package_name; use base qw($package_list);"; eval $declaration; die "declaration error: $@\n" if $@; return bless $object, $package_name; } my $obj = Object->new; $obj->speak; mix ( $obj, 'mixin_simple' ); $obj->speak; $obj->yell; print "object is still an Object\n" if $obj->isa('Object'); my $whisperer = Object->new; mix ( $whisperer, 'mixin_shadow' ); $whisperer->speak; print "whisperer is still an Object\n" if $whispererpk->isa('Object' +);

This is just straight multiple inheritance, but we've created a container package so that the composition can happen on the fly. There's at least one more bit of complexity that needs to be added, though. I'm going to be creating a lot of each kind of these objects, so we should probably be caching the package creation.

sub mix { my ( $object, @classes ) = @_; push @classes, ref $object; my $package_name = join ( '_', @classes ); my $package_list = join ( ' ', @classes ); my $check = "package $package_name; \$declared"; unless ( eval $check ) { print " -- creating new package $package_name -- \n"; my $declaration = "package $package_name; use base qw($package_list); use vars '\$declared'; \$declared++"; eval $declaration; die "declaration error: $@\n" if $@; }; bless $object, $package_name; }

So I lied: it's 16 lines, not 10. Dave Rolsky actually does the caching a much cleaner way, but I've posted what I wrote before I found his code.

A bit of context might help to explain why this is a useful thing to be able to do. In XML::Comma, document structures are made up of different kinds of elements. There are various ways to extend and configure elements (macros, includes, references to other element definitions in the system), but it would also be useful -- for tmtowtdoi, among other, reasons -- to be able to write module-level mixin classes to alter element behavior. Definitions could then look something like this:

<blob_element> <name>picture</name> <mixin>XML::Comma::Mixin::Standard_Image</mixin> <mixin>XML::Comma::Mixin::Auto_Versioning</mixin> </blob_element>

I'm interested in what other monks have to say about this scheme. I'll further encourage discussion by listing some of my thoughts and questions, in no particular order ...

Michael Schwern's mixin module uses a more sophisticated strategy than the simple base-concatenating that I've done here. He walks the symbol table of the mixin classes, globbing CODE references into the use'ing class. This is quite nice, and lets him implement an extra bit of structure that I have mixed feelings about: private methods (those with names prefixed by an "_"), are not mixed. But I've always been enamored of perl's laissez-faire access rules. If you want to call a private method of your "parent" class, that's okay with me -- you presumably know that you're doing something not-quite-kosher.

The other mixin-ism that I'm not comfortable with is the requirement that mixin and mixed classes must share a common base class. What does this buy you?

Why, as mentioned above, do I think about what I'm doing here as using mixin classes, rather than decorators? Because a decorator framework generally implies that decorators need to be aware that that's what they are, and adjust their assumptions and calling practices accordingly. In particular, decorators often redispatch method calls up (or across, really) the object chain, and decorator frameworks require that you do this in some particular way (you can't just use SUPER).

Again, maybe this is what I ought to want, but at the moment anyway, I don't. (More news at eleven.) XML::Comma has hooks that accomplish much the same thing as redispatch-oriented decorators. (And when you type "hook," you feel like you're floating along in lisp-land, not in C++-hell. Much more cozy. Or maybe that's just me.)

Kwindla

Replies are listed 'Best First'.
Re: very simple per-object mixins
by perrin (Chancellor) on Nov 05, 2003 at 18:07 UTC
    When people say "mxin" around Perl, they usually seem to be talking about things that magically add methods to an existing package. That always sounded like a really bad idea to me: "action at a distance" in a big way.

    What you're doing -- changing inheritance at runtime -- seems different to me. Still scary, but not as scary as mxins. One other approach would be to use something like a "strategy pattern." If that seems too heavy, it could be made more lightweight by using sub references in place of actual objects to implement the hooks.

      This is an odd phenomena.

      I agree with you that when I sit down and describe what mixins do, I think that they have to be bad. Bad for the same reason that it is better to use @EXPORT_OK than @EXPORT.

      However when I actually saw them and played with them in Ruby, they were very nice, integrated very well, and the examples that I saw were a joy to play with.

      After some thought, I think that my closing thoughts at Pass by reference vs globals applies. In Ruby when people use mixins, they usually are mixing in a handful of base modules like Enumerable. Because these modules are mixed in everywhere - including in every part of the Ruby library where they make sense - they become something that you just reach for and always find. The cost of learning the interface is paid for because you get the benefit of using it everywhere in Ruby. And what is added really is useful, lots of utility methods that you would have written yourself time after time again. Methods that you can depend on to always be there, under the name that you expect.

      Therefore my current (and highly provisional) opinion is that mixins are a bad idea unless you do them a lot. If you do them pervasively in a very predictable way, then they can become a good thing because the effort of learning the interface and potential conflicts are amortized over a lot of benefit of relying on the benefits to be there.

      A consequence of that opinion is that in languages (like Perl) where people don't use mixins pervasively, attempting to introduce them is a bad idea because you won't get to the point where the cost/benefit ratio is in your favour.

Re: very simple per-object mixins
by autarch (Hermit) on Nov 07, 2003 at 16:19 UTC
    Actually, Class::ClassDecorator only runs calls to $foo->super::bar through the AUTOLOAD. Everything else uses either Perl's normal dispatch. And if you use the "decorate" function, you will be using NEXT.pm instead of my hacked up super package. They each have slightly different dispatch semantics, so you can pick the one you like, but either way the vast majority of method calls will use Perl's normal dispatch.
      Ahh, thanks for the correction. I (badly) misread your code. But I do quite like your hacked-up super calling convention; seems like it could be quite useful to reorder the inheritance tree in that way.