japhy has asked for the wisdom of the Perl Monks concerning the following question:

This is related to the regex parser module. I'm trying to fix a problem encountered when trying to use multiple user-defined add-on modules.

The base class (for these purposes) for a "quant" object is Rx::quant. If you write an add-on, MyRxA, then a MyRxA::quant object's @ISA is (Rx::quant). If you write an add-on, MyRxB, then a MyRxB::quant object's @ISA is (Rx::quant).

MyRxA::quant::raw() changes the output of all quantifiers to '%' characters; this means a+b would render as a%b.

package MyRxA::quant; sub raw { my $self = shift; return "%"; }
MyRxB::quant::visual() makes the quantifier appear before the thing it quantifies; a+b would render as +ab.
package MyRxB::quant::visual; sub visual { my $self = shift; return $self->raw . $self->{data}->visual; }

Now we want to write MyRxC, which uses MyRxA and MyRxB as base classes. This means we want a+b to render as %ab. (Confusing? Sorry. Just bear with me.) The problem is that, because of the way @ISA is set up, an object of MyRxC::quant has the following inheritence tree:

This means any call to a method of a MyRxC::quant object would only be re-dispatched to the MyRxA::quant namespace, and no method in MyRxB::quant would ever be called.

I haven't considered the more difficult scenario of two classes overriding the same method.

I'm stuck. I'm not sure how to traverse the @ISA tree properly. I don't think NEXT has the right tools for the job. I kind of want an "OVER::", not a "NEXT::" or "SUPER::". It looks like I want a breadth-first @ISA scan, not a depth-first scan. What I really want is to avoid looking in Rx for the method until the VERY end. Maybe the solution, then, is not to put Rx in the @ISA tree, but rather to look in Rx after all others have failed, via AUTOLOAD?

_____________________________________________________
Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

Replies are listed 'Best First'.
Re: Multiple Inheritence, Munging @ISA
by Stevie-O (Friar) on Jul 07, 2004 at 00:20 UTC
    You're missing part of the picture.

    You're right -- any call to a method of a MyRxC::quant object will be dispatched to MyRxA::quant.

    Since MyRxA::quant does not define the 'visual' method, its @ISA tree will recursively be searched. This is simply Rx::quant, from what I understand. If Rx::quant defines the 'visual' method, then the Rx::quant one is called instead. Otherwise, Rx::quant's @ISA is checked, depth first, for a 'visual' method.

    If no 'visual' method is found in MyRxA::quant, then MyRxB::quant (the 2nd entry in @MyRxC::quant::ISA) is checked for 'visual'. Thus, the MyRxB::quant::visual() method will be called.

    Now, if Rx::quant defines a 'visual' method (you didn't actually SAY this, but I have a sneaking suspicion that this is why you made your post), you will have to resolve the issue this way:

    package MyRxC::quant; use base 'MyRxA::quant'; use base 'MyRxB::quant'; sub raw { my $this = shift; $this->MyRxA::quant::raw(@_); } sub visual { my $this = shift; $this->MyRxB::quant::visual(@_); }
    Explicitly qualifying the package name in the method call will override the usual method lookup. The $this->SUPER::foo() syntax is actually a special case that automagically handles @ISA-ness.

    Note that, if you are ONLY delegating and not adding any of your own code, *and* you KNOW for a fact that MyRxA and MyRxB respectively define 'raw' and 'visual' themselves (i.e. instead of inheriting them), a slightly faster technique may be used:

    package MyRxC; *raw = \&MyRxA::quant::raw; *visual = \&MyRxB::quant::visual;
    This would effectively import the 'raw' and 'visual' methods from the corresponding packages into MyRxC, bypassing the extra call induced with the "$this->MyRxA::quant::raw()" code. The tradeoff is that the former technique plays fine with inheritance, while this latter technique only works if MyRxA and MyRxB directly contain the 'raw' and 'visual' subs in their respective namespaces.
    --Stevie-O
    $"=$,,$_=q>|\p4<6 8p<M/_|<('=> .q>.<4-KI<l|2$<6%s!<qn#F<>;$, .=pack'N*',"@{[unpack'C*',$_] }"for split/</;$_=$,,y[A-Z a-z] {}cd;print lc
Re: Multiple Inheritence, Munging @ISA
by dragonchild (Archbishop) on Jul 07, 2004 at 01:25 UTC
    You're not going to want to hear this - there is no easy way in Perl5 to do this. In Perl6, this is really easy - you simply redispatch and the dispatcher knows which method to check next. (I actually asked Larry about this situation prior to A12.)

    The best thing I can think of is to write your own method dispatcher. This would only work with methods that have a void signature. Maybe something like:

    sub find_funcs { my $class = shift; my $meth = shift; exists ${$class}{$meth}{SUB}; } sub dispatch_method { my $self = shift; my $meth = shift; my @args = @_; no strict 'refs'; my $pkg = ref($self); # Recursively find the packages that have the $meth function defin +ed # Call them in whatever order you want to define }

    Or, something like that. :-)

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

Re: Multiple Inheritence, Munging @ISA
by perrin (Chancellor) on Jul 07, 2004 at 03:04 UTC
    It kind of sounds like inheritance is the wrong solution here. Maybe what you need is more of a pipeline approach, i.e. each of the plug-ins gets added to the pipeline and they all get run in sequence.
Re: Multiple Inheritence, Munging @ISA
by cees (Curate) on Jul 07, 2004 at 01:35 UTC