I posted a few days ago about a dynamic inheritance problem I had encountered and the workaround I had adopted. Thanks to the feedback I received, I've switched to a new approach.

As I described, I was trying to use mixin classes to add combinations of optional functionality on top of a base class:

+---------+ +---------+ +---------+ | Filter | | Safe | | Base | +---------+ +---------+ +---------+ | | | +------------------+------------------+ v +-------------+ | Safe_Filter | @ISA=qw(Filter Safe Base); +-------------+

The problem lay in re-dispatching method calls via SUPER:: when a mixin class did not directly inherit from the base class.

As several people pointed out, there are alternatives to the SUPER mechanism, such as NEXT. Once I came to terms with the fact that the SUPER:: dispatch system is somewhat arbitrary, I realized that the easiest solution is just to use a custom method to perform the redispatch. If instead of calling $self->SUPER::method( @_ ) I had my mixins call $self->SUPER('method', @_ ), then I could intercept each of those calls and control which method got called next.

In the end I named this method NEXT, because it is reminiscent of the NEXT module, although the direct method call syntax avoids the need for AUTOLOAD.

For example, here's how the exception-catching mixin used to work:

BEGIN { push @MIXIN, "#line ".__LINE__.' "'.__FILE__.'"', "", <<'/' +} sub compile { eval { local $SIG{__DIE__}; (shift)->SUPER::compile( @_) }; } /

Here's the equivalent method using the new calls:

sub compile { eval { local $SIG{__DIE__}; (shift)->NEXT('compile', @_) }; }

The base class provides this implementation for the NEXT method:

sub NEXT { my ( $self, $method, @args ) = @_; my $calling_package = caller(0); my @classes = ref($self) || $self; my @isa; while ( my $class = shift @classes ) { push @isa, $class; no strict; unshift @classes, @{ $class . "::ISA" }; } while ( my $class = shift @isa ) { last if ( $class eq $calling_package ) } while ( my $class = shift @isa ) { next unless my $sub = $class->can( $method ); return &$sub( $self, @args ); } Carp::croak( "Can't find NEXT method" ); }

Does anyone know how to reliably extract the name of the calling method, so that I can ommit that from the calls to the NEXT method? The ( caller(0) )[3] can be hidden inside evals and such; do I just walk back through stack frames until I find one that's not an eval?

Thanks again for your feedback!


In reply to Solving the SUPER problem in Mixins with a Dispatch Method by simonm

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.