Skip Menu |

This queue is for tickets about the Class-Mixer CPAN distribution.

Report information
The Basics
Id: 75108
Status: resolved
Priority: 0/
Queue: Class-Mixer

People
Owner: Nobody in particular
Requestors: mst [...] shadowcat.co.uk
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: Expand on C3-style usage?
Date: Sat, 18 Feb 2012 05:36:16 +0000
To: bugs-Class-Mixer [...] rt.cpan.org
From: Matt S Trout <mst [...] shadowcat.co.uk>
DBIx::Class (ab)uses C3 MRO to do components that need to end up sensibly ordered in what turns out to be a pretty nice way (were I to do it again, I'd probably do roles, but it does work well as is). I see that you have ::C3 in your see also, but it strikes me that given "use mro 'c3';" is now a core feature, it might be worth expanding on why this is better than it. (I'm not intending to use your module right now ... but I can entirely see why it might be useful to people, and think it's worth expanding a bit more on the "why" to make that more obvious) -- Matt S Trout - Shadowcat Systems - Perl consulting with a commit bit and a clue http://shadowcat.co.uk/blog/matt-s-trout/ http://twitter.com/shadowcat_mst/ Email me now on mst (at) shadowcat.co.uk and let's chat about how our Catalyst commercial support, training and consultancy packages could help your team.
Why use Class::C3? mro was not in perl-5.6, and I started developing the module on that version, so I was intending to be backward compatible that far. I suppose I should use MRO::Compat instead.
Subject: Re: [rt.cpan.org #75108] Expand on C3-style usage?
Date: Sat, 18 Feb 2012 06:10:34 +0000
To: John Williams via RT <bug-Class-Mixer [...] rt.cpan.org>
From: Matt S Trout <mst [...] shadowcat.co.uk>
On Sat, Feb 18, 2012 at 12:52:19AM -0500, John Williams via RT wrote: Show quoted text
> <URL: https://rt.cpan.org/Ticket/Display.html?id=75108 > > > Why use Class::C3? > > mro was not in perl-5.6, and I started developing the module on that > version, so I was intending to be backward compatible that far. > > I suppose I should use MRO::Compat instead.
I'm not suggesting *you* should use Class::C3 necessarily. I am saying you should explain more about how Class::Mixer usage compares to the way Class::C3::Componentised based stuff like DBIx::Class works because that would probably help people understand why they might want to use Class::Mixer. I only support 5.8.1+ though, unsure if C3 will do 5.6 -- Matt S Trout - Shadowcat Systems - Perl consulting with a commit bit and a clue http://shadowcat.co.uk/blog/matt-s-trout/ http://twitter.com/shadowcat_mst/ Email me now on mst (at) shadowcat.co.uk and let's chat about how our Catalyst commercial support, training and consultancy packages could help your team.
Thanks for the pointer to Class::C3::Componentised. I had not noticed that module before, and I can see the similarities. How does this sound? Am I correct in saying that Componentised has good support for run-time mixins? =head1 Comparison with Class::C3::Componentised Class::C3::Componentised is used in DBIx::Class similarly to Class::Mixer to implement the same sorts of mixin behaviors. The difference is exemplified by this quote from DBIx::Class::Component, "The order in which is you load the components may be very important, depending on the component. If you are not sure, then read the docs for the components you are using and see if they mention anything about the order in which you should load them." With Class::Mixer, the user never has to deal with the complexities of component order. Component ordering requirements are both documented and *enforced* by the inheritance rules in each component. So when building his application class, he can list the desired component modules in any order, and let Class::Mixer put them in a correctly functioning order. On the other hand, Class::Mixer has no support for run-time mixins, like Class::C3::Componentised->load_components. The class is considered closed after an object is instantiated from it.
Subject: Re: [rt.cpan.org #75108] Expand on C3-style usage?
Date: Sat, 18 Feb 2012 08:58:55 +0000
To: John Williams via RT <bug-Class-Mixer [...] rt.cpan.org>
From: Matt S Trout <mst [...] shadowcat.co.uk>
On Sat, Feb 18, 2012 at 02:37:09AM -0500, John Williams via RT wrote: Show quoted text
> <URL: https://rt.cpan.org/Ticket/Display.html?id=75108 > > > Thanks for the pointer to Class::C3::Componentised. I had not noticed > that module before, and I can see the similarities. > > How does this sound?
Not quite right. Show quoted text
> Am I correct in saying that Componentised has good support for run-time > mixins?
Not exactly, but. See below. Show quoted text
> =head1 Comparison with Class::C3::Componentised > > Class::C3::Componentised is used in DBIx::Class similarly to Class::Mixer > to implement the same sorts of mixin behaviors. The difference is > exemplified by this quote from DBIx::Class::Component, > > "The order in which is you load the components may be very important, > depending on the component. If you are not sure, then read the docs > for the components you are using and see if they mention anything about > the order in which you should load them." > > With Class::Mixer, the user never has to deal with the complexities of > component order. Component ordering requirements are both documented > and *enforced* by the inheritance rules in each component. So when > building his application class, he can list the desired component modules > in any order, and let Class::Mixer put them in a correctly functioning > order.
Within reason, C3 does this. Specifically, it provides your 'before' functionality. The diamond inheritance resolution will cause it to die() if you supply an order that can't be resolved to a sensible call order. However, ordering can still matter because of components that wrap behaviour - consider two components, one that adds logging to a method, and one that adds an authentication check. Which one ends up first will decide whether methods that don't pass the check still trigger the logging. I don't believe Mixer fixes that either though. Show quoted text
> On the other hand, Class::Mixer has no support for run-time mixins, > like Class::C3::Componentised->load_components. The class is considered > closed after an object is instantiated from it.
load_components is nothing to do with "run time mixins". it's just a fancy way of populating @ISA. inject_base can be used to generate a class at runtime though, which is generally the usual approach to doing this sort of thing (systems that provide run-time mixins generally create a new class that inherits from the current one, apply the mixin to that, and rebless the object into it) -- Matt S Trout - Shadowcat Systems - Perl consulting with a commit bit and a clue http://shadowcat.co.uk/blog/matt-s-trout/ http://twitter.com/shadowcat_mst/ Email me now on mst (at) shadowcat.co.uk and let's chat about how our Catalyst commercial support, training and consultancy packages could help your team.
Show quoted text
> However, ordering can still matter because of components that wrap > behaviour - consider two components, one that adds logging to a method, > and one that adds an authentication check. Which one ends up first will > decide whether methods that don't pass the check still trigger the
logging. Show quoted text
> > I don't believe Mixer fixes that either though.
Actually that is exactly the problem Mixer is trying to solve. If they weren't wrapping behaviors, I could just use Traits. As long as one of the modules is aware of the existence of the other, they can declare a constraint to be sure they are put in the right order. If logging should be first in the call order, it declares "before=>Authentication" with an optional clause if needed, or if authentication was written later, it could declare "after=>Logging", which assumes optional. Logging could also be an abstract base class, and any class which declares "isa=>Logging" will be kept together with logging in the inheritance chain. In my structured wiki, the less essential classes usually declare their rules in relation to more essential classes, such as Storage and IO. So for example, the Security class declares "before=>Storage, requires=>Session" because it must be invoked before things are read or written, and it needs a session but doesn't share any methods with it. Session declares "after=>IO" because it needs IO to do process cookies and arguments before it can work. The Index and RCS classes both declare "before=>Storage" but they don't care which of them runs first. Maybe that's the kind of explanation I need?
Subject: Re: [rt.cpan.org #75108] Expand on C3-style usage?
Date: Sun, 19 Feb 2012 19:12:26 +0000
To: John Williams via RT <bug-Class-Mixer [...] rt.cpan.org>
From: Matt S Trout <mst [...] shadowcat.co.uk>
On Sat, Feb 18, 2012 at 08:14:59PM -0500, John Williams via RT wrote: Show quoted text
> <URL: https://rt.cpan.org/Ticket/Display.html?id=75108 > >
> > However, ordering can still matter because of components that wrap > > behaviour - consider two components, one that adds logging to a method, > > and one that adds an authentication check. Which one ends up first will > > decide whether methods that don't pass the check still trigger the
> logging.
> > > > I don't believe Mixer fixes that either though.
> > Actually that is exactly the problem Mixer is trying to solve. If they > weren't wrapping behaviors, I could just use Traits. As long as one of > the modules is aware of the existence of the other, they can declare a > constraint to be sure they are put in the right order. > > If logging should be first in the call order, it declares > "before=>Authentication" with an optional clause if needed, or if > authentication was written later, it could declare "after=>Logging", > which assumes optional.
That allows the classes to specify which one they believe should be first. isa in C3 handles before in that case, as I say, although not optional-ness. I'm talking about situations where the user wants to be able to choose which happens first. Show quoted text
> Logging could also be an abstract base class, and any class which > declares "isa=>Logging" will be kept together with logging in the > inheritance chain. > > In my structured wiki, the less essential classes usually declare their > rules in relation to more essential classes, such as Storage and IO. So > for example, the Security class declares "before=>Storage, > requires=>Session" because it must be invoked before things are read or > written, and it needs a session but doesn't share any methods with it. > Session declares "after=>IO" because it needs IO to do process cookies > and arguments before it can work. The Index and RCS classes both declare > "before=>Storage" but they don't care which of them runs first. > > > Maybe that's the kind of explanation I need?
Modulo the clarification I made above about what I was talking about, yes I think it is. This last reply of yours makes it much much more clear to me what you're achieving with Mixer and I think a section of the docs describing those examples would be a very good idea - it's taken me from "I'm never going to use this" to "I'm probably never going to use this but I'm definitely remembering it exists to steal ideas from if I run into these use cases". So if you add that, and try and summarise my explanations of C3 to indicate more clearly the subset of Mixer's features that "use mro 'c3';" provides (I'll happily proofread that bit) I think you'd have a much better set of documentation. Thanks for keeping discussing this, it's been bloody interesting. -- Matt S Trout - Shadowcat Systems - Perl consulting with a commit bit and a clue http://shadowcat.co.uk/blog/matt-s-trout/ http://twitter.com/shadowcat_mst/ Email me now on mst (at) shadowcat.co.uk and let's chat about how our Catalyst commercial support, training and consultancy packages could help your team.
I was busy last week, but here (attached) is the full updated documentation. I've indicated in the 'before' section that it is exactly like 'use base' with c3, and included the examples we discussed. If you want to edit or comment, feel free.
Subject: class-mixer.pod
=pod =head1 NAME Class::Mixer - Arrange class hierarchy based on dependency rules =head1 SYNOPSIS package Base; use Class::Mixer; sub x { 'Base::x' } package Main; use Class::Mixer before => 'Base'; sub x { 'Main::x' } my $obj = Main->new(); print "@Main::ISA\n"; # prints "Base Class::Mixer" package Mixin; use Class::Mixer before => 'Base', after => 'Main'; sub x { 'Mixin::x' } package NewMain; use Class::Mixer isa => 'Main', requires => 'Mixin'; sub x { 'NewMain::x' } my $obj = NewMain->new(); print "@NewMain::ISA\n"; # prints "Main Mixin Base Class::Mixer" =head1 DESCRIPTION This module is designed to solve a problem which occurs when using inheritance to mixin behaviors into a class hierarchy. The dependencies between a number of mixin modules may be complex. When different components wrap the same behavior, they often need to be in a specific order in the call chain, making it tricky to get the base classes to inherit in the right order. Then if you have a class Main which gets the inheritance right, and you want to add a class Mixin which needs to go in the middle of the inheritance, you cannot simply do C<package NewMain; use base qw(Main Mixin);> because Mixin will be put at the end of the inheritance chain. Also, if you have a class Foo::Better which enhances the Foo behavior, the same problem occurs trying to mixin Foo::Better. And it is even worse if some classes have done C<use base 'Foo';> to try to enforce the correct hierarchy. This module solves these problems by implementing a dependency-based hierarchy. You declare the relations between the classes, and an order of inheritance which will support those relations is determined automatically. For example, if you have a Logging component and an Authentication component, the Logging needs to be called first, because if Authentication fails, it will never be called at all. In the Logging class, one can declare the Mixer rule C<< before=>'Authentication', optional=>'Authentication' >>, so that if Authentication is in the class hierarchy, Logging will be placed before it, but it will not complain if it is not there. Alternatively, one could place the rule C<< after=>'Logging' >> in the Authentication class to achieve the same result. Logging could also be an abstract base class, and any class which declares C<< isa=>'Logging' >> will be kept together with Logging in the inheritance chain. This allows rules to refer to behavior classes without needing to know exactly which behavior class will actually be used. In my own usage, in a structured wiki project, less essential classes usually declare their rules in relation to more essential classes, such as Storage and IO. So for example, the Security class declares C<< before=>'Storage' >> because it must be invoked before records are stored or retrieved, and also C<< requires=>'Session' >> because it needs a session but does not share any behavior methods with it. Session declares C<< after=>'IO' >> because it needs IO to process cookies and arguments before it can work. The Index and Revision classes both declare C<< before=>'Storage' >> but they do not care which of them runs first. Class::Mixer combines functions from base and Class::C3 to do its job. It will C<require> the given classes (unless optional) similar to C<use base>. And it attempts to force c3 semantices, so you should do C<< $self->method::next >> instead of C<SUPER> for inheritance. =head1 Inheritance rules When you design your classes, instead of doing C<use base> or C<our @ISA>, do something like the following: package Example; use Class::Mixer before => 'BaseClass', 'OtherClass', after => 'SoAndSoClass', isa => 'PreviousExample', requires => 'OtherBehavior', optional => 'OtherClass'; The Class::Mixer class provides a basic new() method, which will call init(), which your classes can override. The actual inheritance is computed for a class the first time new() is called. The inheritance rules are described here: =head2 B<before> The 'before' rule means this package must occur before some other class in the method dispatch order, which means that if they both define the same method, this class will be invoked before the other. In the inheritance hierarchy, this class should be a descendant of the other. This is exactly the same as what you would get if you did C<use base> instead. Which you can, and Class::Mixer will notice and use it. If no rule type is given (e.g. C<use Class::Mixer 'BaseClass';> ) then the before rule is assumed. =head2 B<after> The 'after' rule means this package must occur after some other class in the method dispatch order. The other class will usually be a descendant of this one. This is best if used rarely, but it is nice when it is necessary. It is essentially the same as doing C<< before => 'this', optional => 'this' >> in the other class. =head2 B<isa> The 'isa' rule establishes a very strong isa relationship between classes. All the classes are related by C<@ISA>, of course, but this 'isa' means that the particular behaviors implements by the classes are the same, and that this one enhances the other. The classes are kept together as close as possible in the computed hierarchy, and all rules which were applied to the other class will be applied to this class as well. =head2 B<requires> The 'requires' rule says that this class needs the behavior from some other class, but the inheritance order is not important. Usually this is because the two classes do not share any methods. =head2 B<optional> The 'optional' rule doesn't affect the class hierarchy. It simply makes the class not complain if the other class is not there. This is useful when we want to say, "I do not really need this other class, but IF someone else does, I should be before (or after) the other class." =head1 Why not Traits? Traits are complementary to inheritance, and do not address situations where one module needs to extend another by extending/wrapping/inheriting the same method. Traits are more concerned with properly adding new methods to a class, while Class::Mixer tries to solve some problems with complex mixin-style inheritance trees. For example, I have a C<write> method, and several optional behaviors which happen when C<write> is called, such as an Index behavior and an VersionControl behavior. These will both override the C<write> method and call the parent method before or after they do what they need to do. But this violates the flattening property of traits. The C<write> method in Index and VersionControl are not conflicting; they need to inherit, perhaps in a specific order, so that both with be called. It may be that what I am actually describing is event-based, because write is the event, and the behaviors are various things which need to happen when that event occurs. None of the event systems I have looked at have contraints to allow ordering the behaviors relative to each other, so even if I setup an event model, I would still need the solution which Class::Mixer provides. =head1 Comparison with Class::C3::Componentised Class::C3::Componentised is used in DBIx::Class similarly to Class::Mixer to implement the same sorts of mixin behaviors. The difference is exemplified by this quote from DBIx::Class::Component, "The order in which is you load the components may be very important, depending on the component. If you are not sure, then read the docs for the components you are using and see if they mention anything about the order in which you should load them." With Class::Mixer, the user never has to deal with the complexities of component order. Component ordering requirements are both documented and *enforced* by the inheritance rules in each component. So when building his application class, he can list the desired component modules in any order, and let Class::Mixer put them in a correctly functioning order. On the other hand, Class::Mixer has no way to override the rules in the subclasses, so if the user decided he really wanted Authentication to happen before Logging, he would have to change the rules in the subclasses in order to make that happen. =head1 BUGS and TODO Probably. "optional" isn't fully implemented yet. TODO?: implement a 'nota' rule, to prevent someone from putting this class in the same hierarchy as another. =head1 SEE ALSO L<Class::C3> L<Class::C3::Componentised> L<base> The snide comments in L<mixin> probably apply to this module as well. =head1 AUTHOR John Williams, E<lt>smailliw@gmail.comE<gt> Thanks to Matt S Trout for help clarifying the documentation. =head1 COPYRIGHT AND LICENCE Copyright (c) 2009 by John Williams This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut