advice on wrapping methods from a superclass

11 views
Skip to first unread message

Jonathan Swartz

unread,
Feb 10, 2009, 9:02:00 PM2/10/09
to perl-cach...@googlegroups.com
CHI drivers implement methods like remove() and clear(). If you call
$cache->remove(), it goes directly to the driver subclass.

The problem is that there are now legitimate reasons to "wrap" these
methods at the CHI/Driver.pm superclass level (meaning, do something
before and/or after the method). For example, I want to add an
optional generic size-awareness feature (the cache can keep track of
its own size), which means that we have to adjust size whenever
remove() and clear() are called. And I want to log remove() calls the
way we currently log get() and set().

So one solution is to define remove() and clear() in CHI/Driver.pm,
and have them call _remove() and _clear() in the driver subclasses.
But this kind of change makes me uneasy for several reasons:
* It changes the driver API, i.e. all existing drivers out there have
to modified. And we might have to change it again as we identify new
methods to wrap.
* The list of 'normal' versus 'underscore' methods becomes rather
arbitrary - it's "whatever we've needed to wrap so far".

Moose has before & after modifiers, but can they be defined in the
superclass and affect the subclass??

I guess CHI.pm could use a wrapping module, like Sub::Prepend or
Hook::LexWrap, on any driver class the first time it is used, e.g. the
first time someone says

CHI->new(driver => 'File')

we wrap the appropriate File methods. But this feels hacky - these
tools seem like a way to modify someone else's module, not as a
standard part of your own class.

Advice appreciated!

Thanks
Jon

Justin DeVuyst

unread,
Feb 11, 2009, 1:00:52 AM2/11/09
to perl-cach...@googlegroups.com
Jonathan Swartz wrote:
> Moose has before & after modifiers, but can they be defined in the
> superclass and affect the subclass??

Nope.

> I guess CHI.pm could use a wrapping module, like Sub::Prepend or
> Hook::LexWrap, on any driver class the first time it is used, e.g. the
> first time someone says
>
> CHI->new(driver => 'File')
>
> we wrap the appropriate File methods. But this feels hacky - these
> tools seem like a way to modify someone else's module, not as a
> standard part of your own class.

I don't believe that level of hackery is necessary.

The best way I can think of would be to throw your method modifiers
into a role that would be applied to the drivers. You then have the
choice of when and how you want to have that happen. You could
require drivers to apply it themselves or you could force its
application at runtime inside CHI.

-jdv

Jonathan Swartz

unread,
Apr 26, 2009, 10:48:24 PM4/26/09
to perl-cach...@googlegroups.com
I asked about this in Feb, but didn't get any completely satisfying
answers:

> CHI drivers implement methods like remove() and clear(). If you call
> $cache->remove(), it goes directly to the driver subclass.
>
> The problem is that there are now legitimate reasons to "wrap" these
> methods at the CHI/Driver.pm superclass level (meaning, do something
> before and/or after the method). For example, I want to add an
> optional generic size-awareness feature (the cache can keep track of
> its own size), which means that we have to adjust size whenever
> remove() and clear() are called. And I want to log remove() calls
> the way we currently log get() and set().
>
> So one solution is to define remove() and clear() in CHI/Driver.pm,
> and have them call _remove() and _clear() in the driver subclasses.
> But this kind of change makes me uneasy for several reasons:
> * It changes the driver API, i.e. all existing drivers out there
> have to modified. And we might have to change it again as we
> identify new methods to wrap.
> * The list of 'normal' versus 'underscore' methods becomes rather
> arbitrary - it's "whatever we've needed to wrap so far".
>

I thought about using regular wrapping modules, like Sub::Prepend or
Hook::LexWrap. But this fails when you have subclasses more than one
level deep. e.g.:

CHI::Driver -> CHI::Driver::Foo -> CHI::Driver::Foo::Bar

Now if you call CHI::Driver::Foo::Bar::remove(), the wrapping code
will get called twice, once for each subclass. I only want it to be
called once regardless of how deep the subclass is.

Here's how I solved this in CHI-0.2. When each CHI driver is used for
the first time, e.g. CHI::Driver::Memory:

my $cache = CHI->new('Memory');

CHI autogenerates a new class called
CHI::Wrapped::CHI::Driver::Memory, which inherits from

('CHI::Driver::Wrapper', 'CHI::Driver::Memory')

then blesses the actual cache object (and future cache objects of this
driver) as CHI::Wrapped::CHI::Driver::Memory.

Now, when someone calls a method like $cache->get() or $cache-
>remove(), CHI::Driver::Wrapper has an opportunity to handle it
first, and then pass control to CHI::Driver::Memory. If not, it goes
directly to CHI::Driver::Memory.

You can see the code here: http://cpansearch.perl.org/src/JSWARTZ/CHI-0.2/lib/CHI/Driver/Wrapper.pm

I was unable to find this solution on CPAN, even though I feel like I
must be reinventing the wheel. If someone knows of a distribution that
encapsulates this technique, please let me know.

Thanks
Jon

lestrrat

unread,
Apr 26, 2009, 10:56:33 PM4/26/09
to Perl-Cache Discuss
I see you talked about method modifiers in a previous thread, but
won't these work if you changed your classes to roles? Roles will
allow you to add method modifiers the moment you apply the roles:

package BeforeRole;
use Moose::Role;

before foo => sub {
warn "before foo\n";
};

package MainClass;
use Moose;

with 'BeforeRole';

sub foo {
warn "foo!";
}

package main;

my $x = MainClass->new();
$x->foo;

Tim Bunce

unread,
Apr 27, 2009, 5:59:51 AM4/27/09
to perl-cach...@googlegroups.com
On Sun, Apr 26, 2009 at 07:48:24PM -0700, Jonathan Swartz wrote:
>
> You can see the code here: http://cpansearch.perl.org/src/JSWARTZ/CHI-0.2/lib/CHI/Driver/Wrapper.pm
>
> I was unable to find this solution on CPAN, even though I feel like I
> must be reinventing the wheel. If someone knows of a distribution that
> encapsulates this technique, please let me know.

Seems like you're reinventing runtime composition of traits/roles.

http://search.cpan.org/~drolsky/Moose/lib/Moose/Cookbook/Roles/Recipe3.pod
http://search.cpan.org/perldoc?MooseX::Traits
http://blog.jrock.us/articles/MooseX%3A%3ATraits.pod

Tim.

Jonathan Swartz

unread,
Apr 27, 2009, 5:37:54 PM4/27/09
to perl-cach...@googlegroups.com
When you say "changed your classes to roles", do you mean change each
CHI::Driver subclass to a role? Or do you mean just put the wrapping
code (CHI::Driver::Wrapper) in a role?

lestrrat

unread,
Apr 27, 2009, 10:27:38 PM4/27/09
to Perl-Cache Discuss
I must confess I'm not 100% sure what the effects you want to achieve
are, but I'm guuessing -- you probably should make
CHI::Driver::Wrapper a role.

So then, when you create a subcache, you apply a role for wrapped
cache, which introduces a before hook on remove, expire, expires_if,
etc.

--d

Jonathan Swartz

unread,
Apr 28, 2009, 7:24:20 AM4/28/09
to perl-cach...@googlegroups.com

Yup, that's what I was looking for - the ability to apply a role to an
instance, rather than a class. That prevents the double-application-of-
wrapping-code I was running into.

I have to leave my code in for now, because CHI is using Mouse, which
doesn't support applying a role to an instance. But at least I know I
can tear this out in the future.

Thanks!
Jon

Justin DeVuyst

unread,
Apr 28, 2009, 7:49:59 AM4/28/09
to perl-cach...@googlegroups.com

Jon,

This is exactly what I meant back on 2/11/09 when I said
"application at runtime". I guess I could of been clearer
- sorry about that. This also shows how good the new Moose
docs are that Dave Rolsky put together. Nice job Dave!

-jdv

Jonathan Swartz

unread,
Apr 28, 2009, 8:04:02 AM4/28/09
to perl-cach...@googlegroups.com
My fault for not paying closer attention. Also, the idea of applying
roles to instances at runtime sounded inefficient, but of course it is
doing this as efficiently as possible.
Reply all
Reply to author
Forward
0 new messages