Discussion:
two alternate approaches to mixin functionality
Terrence Brannon
2004-12-05 02:18:13 UTC
Permalink
NAME
Class::MixinFactory::alternatives - approaches to the same functionality

DESCRIPTION
A recent contribution to CPAN is Class::MixinFactory by Simon Cavaletto.
Here we show two more ways to do the same thing in addition to the ones
that Simon lists in the SEE ALSO section.

One method relies on Leon Brocard's Language::Functional. The other
relies on Toby Ovod-Everett's Class::Prototyped.

ALTERNATIVE IMPLMENTATIONS
I include here a file, which when run, produces output similar to the
output from factory_class.t in the Class::MixinFactory distribution.

It shows two new ways of doing this, the Language::Functional approach
and the Class::Prototyped approach.

#
# lf.pl
#

use strict;

use Language::Functional ':all';

sub _uc { uc shift } ;
sub bold { sprintf "<b>%s</b>", shift } ;
sub italics { sprintf "<i>%s</i>", shift } ;

my @telescope = (\&_uc, \&bold, \&italics) ;

my $o = foldl { my $seed = shift; my $func = shift; $func->($seed) }
"hello, world!", \@telescope;

warn $o;

# ----------------------------------------------------------

use Class::Prototyped ':EZACCESS';

sub add2 { $_[1] + 2 };

my $po;
$po = Class::Prototyped->new
(
uc_m => sub { uc $_[1] },
italics_m => sub { italics $_[1] },
bold_m => sub { bold $_[1] }
);

my @agenda = qw(uc_m italics_m);
my @agenda2 = qw(bold_m italics_m uc_m);

my $ret = foldl { my $seed = shift; my $meth = shift; $po->$meth($seed) }
"hello, world!", \@agenda;

warn $ret;

$ret = foldl { my $seed = shift; my $meth = shift; $po->$meth($seed) }
"hello, world!", \@agenda2;

warn $ret;

EVALUATION
We can compare these two implementations in a number of ways -
readability, dynamic utility, object power, and sub power.

Readability
Of the two implementations, the functional implementation is the easiest
to read and follow.

Dynamic utility
The prototype implementation appears to be easier to manipulate
dynamically. What I mean by easier to manipulate dynamically is that all
one need do is specify a list of strings in order to build up a sequence
of operations. Strings can come from many places, such as form data,
databases, STDIN, etc. With the functional version, you would need to
build yourself a dispatch table in order to get similar functionality.
The table building is an intrinsic aspect of using Class::Prototyped. It
must be done manually for the functional version.

Object power
Prototyped objects are powerful and flexible. They give one the
advantage of objects without all the class-building. The functional
version will not have the advantages of prototyped objects without
creating objects and then references to the subroutines of those objects
(I don't even know if that is possible).

Sub power
An advantage of the functional implementation is that you can use normal
monadic functions just by passing a reference to them. With the
prototyped implementation, you have to create an object wrapper for
them.

CONCLUSION
Perl offers a wealth of ways to solve problems. Simon's recent
Class::MixinFactory solved a number of problems with the Mixin approach
to runtime generation of "agendas for mixing behavior." He also makes
mention of a number of other approaches and perhaps should mention
Class::Delegation in addition.

In this article, I have shown two more ways to mix behavior. The actual
one to choose for software design remains open based on other aspects of
the intended application.

SEE ALSO
* Language::Functional
* Class::Prototyped
* CGI::Prototype
That's right, if you like the prototyped way of doing this, there is
a web application framework developed by Randal Schwartz which is
based on Class::Prototyped. It is well thought out, well-designed
and covers the functionality offered by earlier MVC frameworks with
powerful, featureful controllers such as CGI::Application and Zanas.

* The Perl Software::Design Mailing list
<http://www.metaperl.com/sw-design/>

AUTHOR
U-MOKSHA\metaperl, <***@metaperl.com>

COPYRIGHT AND LICENSE
Copyright (C) 2004 by U-MOKSHA\metaperl

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself, either Perl version 5.8.2 or, at
your option, any later version of Perl 5 you may have available.

To download the CPAN-style distro with working source code, visit:
http://www.metaperl.com/article-pod/Class-MixinFactory-alternatives-0.01.tar.gz
Terrence Brannon
2004-12-05 04:06:17 UTC
Permalink
You can now read/post this mailing via GMANE:

http://dir.gmane.org/gmane.comp.lang.perl.software-design

--
Carter's Compass: I know I'm on the right track when,
by deleting something, I'm adding functionality.
Mark Stosberg
2004-12-06 00:42:53 UTC
Permalink
Post by Terrence Brannon
NAME
Class::MixinFactory::alternatives - approaches to the same functionality
DESCRIPTION
A recent contribution to CPAN is Class::MixinFactory by Simon Cavaletto.
Here we show two more ways to do the same thing in addition to the ones
that Simon lists in the SEE ALSO section.
What thoughts do people have on the solution that uses standard export
techniques without extra modules? This is what CGI::Application Plugins do
now.

What to mixin a CGI::Application plug-in?

use CGI::Application::Plugin::ValidateRM (qw/check_rm/);

That's it. It's mostly a standard 'has-a' relationship.

The only thing slightly unique going on is that the imported

functions expect the first argument to be a CGI::Application compatible
object.

You can mix-in these methods and then have them later inherited, or just
use them directly in the children.

So far I've seen that's very clear to the CGI::App users and the plug-in
writers how to use the system, because nothing really unfamiliar is
happening.

We have about half a dozen plug-ins using the system so far and haven't
run into any real snags that I've noticed.

Mark
--
http://mark.stosberg.com/
Stevan Little
2004-12-06 04:37:34 UTC
Permalink
Mark,
Post by Mark Stosberg
What thoughts do people have on the solution that uses standard export
techniques without extra modules? This is what CGI::Application Plugins do
now.
This approach is indeed simple to use and understand, but I also think
it has a number limitations.

<disclaimer>
I am not familiar with the details of CGI::Applications's plugins so I
may be off here. My assumption is that the plug-ins import themselves
into the CGI::Application namespace (or a subclass of CGI::Application
maybe).
</disclaimer>

Anyway, this technique has all the frailty of most other mix-in
technologies in that its injecting methods into the class hierarchy in
an arbitrary way. This makes it difficult to have much control over the
hierarchy since at any point a mix-in can just inject functionality
into the middle of it. This also makes debugging difficult since you
now have methods coming at you from all sides. IMO this just gets too
messy and impossible to control in any meaningful way.

If you have a well defined plug-in API and a set of overridable methods
then this can work, since this becomes a predictable thing. But at that
point you are not using general purpose mix-ins, but specific
constrainted plug-ins.
Post by Mark Stosberg
That's it. It's mostly a standard 'has-a' relationship.
A mix-in is not necessarily a has-a relationship. You can use it to
implement such a thing, but usually a mix-in is defined as sort of a
half-hearted is-a relationship. Mix-in classes are many times partial
(deferred) classes (see some of the Python HTTP server classes), so to
call it a true is-a is IMO not correct. When you are combining several
full-classes, I prefer to just call that multiple-inheritance. Although
in practice you can rarely draw clean lines. For instance Simon (in
Text::MicroMason which gave birth to Class::MixinFactory) seemed to
have a combination of whole and partial classes (at least that is what
it seemed like to me after a quick read of the code and several
discussions on perlmonks).

Anyway my point is that many times what you want with a mix-in is an
'is-a' relationship. With the standard export technique this is not
possible.

It can also get messy depending upon the how and when you do things.
Starting with the "how", assuming that you have class A and are mixing
in B.

mixin B {
export "test";
}

class A {
use mixin B;
sub test { ... }
}

Does B.test override A.test? My first guess would be no, since in most
inheritance relationships the local class overrides the superclass.
However looking at this it seems that they did intend to mixin B, which
would include B.test? These is ambiguity here in the syntax and in the
mix-ing in mechanism. The standard export mechanism has no intelligence
built into it so things can quickly get confusing.

Now for the "when". Lets look at this from a different angle:

mixin B {
export "test";
}

class A {
# we do not do the mix-in here anymore
sub test { ... }
}

class C isa A {
sub test {
...
super()
}
}

# some time later in your code
mixin B into A;

Now, I would safely assume that B.test should override A.test. The
explicit mix-in outside of the class makes me assume that. Now in class
C, the super() call would go to B.test and not A.test, which quite
possibly could do things that the author of C is not expecting. This is
a potential maintenance headache, you are getting very quickly into
spaghetti code territory here.

Now these examples may seem trivial, but start mixing in more than one
class and the issues can grow exponetially.

Simon actually explains this stuff and more in the Class::MixinFactory
docs
(http://search.cpan.org/~evo/Class-MixinFactory-0.92/MixinFactory/
ReadMe.pod) Its a good read if you are interested. Also for a more
esoteric bend on all this you can check out the papers on traits
(http://www.iam.unibe.ch/~scg/Research/Traits/), of which I wrote a
perl implementation of
(http://search.cpan.org/~stevan/Class-Trait-0.04/lib/Class/Trait.pm).


- Steve
Matthew Simon Cavalletto
2004-12-08 19:35:07 UTC
Permalink
Post by Mark Stosberg
What thoughts do people have on the solution that uses standard export
techniques without extra modules? This is what CGI::Application Plugins do
now.
Yup; this "method exporting" is a workable solution that I've used in
the past, and it seems a reasonable choice if your callers are building
their own subclasses anyway.

Because you're importing those methods into custom constructed
subclasses of CGI::Application, this implementation doesn't suffer from
the worst of the namespace confusion that Stevan referred to in his
reply.

A second area of concern is that it lacks a clear re-dispatch mechanism
to allow the plugins/mixins to invoke underlying behaviors. For
example, a mixin can't simply override a built-in method to provide
benchmarking information:

package CGI::Appliction::Plugin::RunTimeLog;
@EXPORT = 'run';
sub import { goto Exporter::import }

sub run {
my $start = Benchmark->new();
(shift)->SUPER::run( @_ ); # <== XXX dies here XXX
warn "Run: " . timestr( $start->timediff( Benchmark->new() ) )
}

The SUPER call won't work because the plugin doesn't inherit from
anything. I'm not sure if the NEXT module would properly handle this
situation, although I think it might, and it would be easy to test.

-Simon
Mark Stosberg
2004-12-10 02:47:48 UTC
Permalink
Post by Matthew Simon Cavalletto
Yup; this "method exporting" is a workable solution that I've used in
the past, and it seems a reasonable choice if your callers are building
their own subclasses anyway.
That is typically the case with CGI::Applicaton.
Post by Matthew Simon Cavalletto
A second area of concern is that it lacks a clear re-dispatch mechanism
to allow the plugins/mixins to invoke underlying behaviors. For
example, a mixin can't simply override a built-in method to provide
package CGI::Appliction::Plugin::RunTimeLog;
@EXPORT = 'run';
sub import { goto Exporter::import }
Why the non-standard Exporter syntax? The docs recommend:

@ISA = qw(Exporter);
@EXPORT_OK = qw(munge frobnicate); # symbols to export on request
Post by Matthew Simon Cavalletto
sub run {
my $start = Benchmark->new();
warn "Run: " . timestr( $start->timediff( Benchmark->new() ) )
}
There is a proposal to handle this in part with
CGI::Application::Callbacks:

http://cees.crtconsulting.ca/perl/modules/CGI-Application-Callbacks-0.01.tar.gz

This a mechanism to allow you to declare that you want to run a plug-in
at each stage of the CGI::App pipeline.
Post by Matthew Simon Cavalletto
The SUPER call won't work because the plugin doesn't inherit from
anything. I'm not sure if the NEXT module would properly handle this
situation, although I think it might, and it would be easy to test.
I see-- A mix-in like this can't usefully override a method and call it too.

For the CGI::App case, hooks handle that well enough.

Your explanation was of the drawbacks was helpful, though!

Mark
--
http://mark.stosberg.com/
Matthew Simon Cavalletto
2004-12-08 18:46:50 UTC
Permalink
Post by Terrence Brannon
A recent contribution to CPAN is Class::MixinFactory by Simon Cavaletto.
Here we show two more ways to do the same thing in addition to the ones
that Simon lists in the SEE ALSO section.
Terrence -- Thanks for your article, and for your attention to this
issue.

However, I'm not convinced that these alternatives "do the same thing"
as Class::MixinFactory on other than a very abstract level.

Both of the examples use foldl() to chain several function calls
together, but they don't do any of the following:

1. Create polymorphic instances, which hide different behaviors behind
an identical-looking simple calling interface;

2. Allow behaviors for several functions to be grouped together, so
that one user-selectable option can influence multiple methods;

3. Allow behaviors to control how items later in the chain are called,
so that they can wrap them in an eval, cache the results, do
something before the call and then clean up after, etc.

Issue 1 is fairly easy to address, by replacing the foldl call with a
call to a dynamically built single function that calls a selection of
others, and I've included some sample code in a post-script below that
extends your examples in this way.

But issues 2 and 3 are harder to address using the approaches you've
shown. Issue 3 in particular seems to mandate some type of explicit
redispatch mechanism as opposed to simple pipelining of function
results.

Using Class::Prototyped and multiple parents does allow you to solve
issue 2; each collection of behaviors can be made into a prototyped
object and then your instances can specify some selection of them as
parents. However, to support the wrapper methods for issue 3, you'd
need to add support for NEXT-style redispatch, as Class::Prototyped
only implements an equivalent to SUPER.
Post by Terrence Brannon
He also makes mention of a number of other approaches and
perhaps should mention Class::Delegation in addition.
Thanks; I'll add that to the "RELATED MODULES" section of the
ReadMe.pod.

-Simon

----------------------------------------------------------------------

# POST-SCRIPT WITH EXAMPLES

Rather than requiring callers to go through the foldl() rigamarole, I'd
much rather hide this implementation behind a simple function-call or
method-call interface.

It's easy to construct curried functions:

sub build_func {
my @funcs = @_;
sub {
my $seed = shift;
$seed = $_->( $seed ) foreach ( @funcs );
$seed;
}
}

my $viewer_1 = build_func(\&_uc, \&bold, \&italics);

my $viewer_2 = build_func(\&italics, \&bold);

warn $viewer_1->("hello, world!");

And using Class::Prototyped, you could create instances that had those
curried functions installed as methods.

sub build_method {
my @methods = @_;
sub {
my ( $self, $seed ) = @_;
$seed = $self->$_( $seed ) foreach ( @methods );
$seed;
}
}

my $views = Class::Prototyped->new(
uc_m => sub { uc $_[1] },
italics_m => sub { italics $_[1] },
bold_m => sub { bold $_[1] }
);

my $viewer_1 = Class::Prototyped->new(
'*' => $views,
'view' => build_method( qw/ uc_m bold_m italics_m / ),
);

my $viewer_2 = Class::Prototyped->new(
'*' => $views,
'view' => build_method( qw/ italics_m bold_m / ),
);

warn $viewer_1->view("hello, world!");
Loading...