shootnix
shootnix

Reputation: 315

Perl: How to import subroutines from base class?

I have a base class, named Foo::Base, I need to inherit its methods, like 'new' and to import some subroutines names in a scope:

package Foo::Base;

sub new { ... }

sub import {
    no strict 'refs';

    my $caller = caller;

    *{"${caller}::my_sub"} = sub { 1 };
}

1;

So, I need to use this base class in my second class, Foo::Child:

use base 'Foo::Base';

... and it works for inheritance, but it doesn't import 'my_sub' in a scope. I can add string

use Foo::Base;

for it and it helps, but I don't want to write something like this:

use base 'Foo::Base';
use Foo::Base;

This is looks kind of wierd... Is there any suggestions for this problem?

Upvotes: 6

Views: 2413

Answers (3)

Joe McMahon
Joe McMahon

Reputation: 3382

To clarify a little more the question of "but why doesn't the import in Foo::Child work?", here's a program that will spell out what's actually happening:

use Foo::Child;
print my_sub(); # Really? Yes, really!
print Foo::Child::my_sub()," done\n";

and added this to the Foo::Base::import() routine:

print "Caller is $caller\n";

When you run this, you see this as the output:

Caller is main
1
Undefined subroutine &Foo::Child::my_sub called at foo_user.pl line 4.

We see Foo::Base reporting in, letting us know who the caller is: it's the main program! We see the '1', which proves that yes, main::my_sub now exists, and then the failure because the import went to the wrong namespace.

Why is this? Because the import process is all being handled by the main program. Foo::Base's import doesn't get called by anything in Foo::Child; it's getting called by the main program in the process of loading the modules. If you really, really want to force subs, as opposed to methods, to import into Foo::Child, you need to explicitly make it happen in Foo::Child itself. The 'use Foo::Base' will do it, as that makes the import execute with Foo::Child as the caller; if you insist on the import, but the double use squicks you out too much, you can call Foo::Base::import() right after the 'use base'. This is exactly what the double 'use' does anyway.

Nevertheless, I like Schwern's class methods better, and I recommend that alternative.

Upvotes: 1

Schwern
Schwern

Reputation: 165198

There's two reasons one might want to do what you're doing, both of them are bad.

First is you're trying to import methods from your parent class... for some reason. Maybe you misunderstand how OO works. You don't need to do that. Just call inherited methods as methods and unless those methods are doing something wacky it will work fine.

More likely is this a mixed-use module where some of it is methods and some of it is imported functions. And for that you can do...

use base 'Foo::Base';
use Foo::Base;

And you rightly observed that it looks kind of weird... because it is kind of weird. A class that also exports is mixing idioms, and that's going to result in weird usage patterns.

Best thing to do is to redesign the class to instead of exporting functions, either split the functions out into their own module, or make them class methods. If the functions really don't have much to do with the class, then its best to spin them off. If they do relate to the class, then make them class methods.

use base 'Foo::Base';

Foo::Base->some_function_that_used_to_be_exported;

This eliminates the interface mismatch, and as a bonus, subclasses can override class method behavior just like any other method.

package Bar;

use base 'Foo::Base';

# override
sub some_function_that_used_to_be_exported {
    my($class, @args) = @_;

    ...do something extra maybe...

    $class->SUPER::some_function_that_used_to_be_exported(@args);

    ...and maybe something else...
}

If you don't have control over the base class, you can still make the interface sane by writing a subclass which turns the exported functions into methods.

package SaneFoo;

use base 'Foo::Base';

# For each function exported by Foo::Base, create a wrapper class
# method which throws away the first argument (the class name) and
# calls the function.
for my $name (@Foo::Base::EXPORT, @Foo::Base::EXPORT_OK) {
    my $function = Foo::Base->can($name);
    *{$name} = sub {
        my $class = shift;
        return $function->(@_);
    };
}

Upvotes: 14

Axeman
Axeman

Reputation: 29854

When you write use base, you're using the facilities of the base module. And you're passing it the parameter of of the module which you want to be your base class.

In an OO IS-A relationship, no import needed. You call the methods with the OO-pattern: $object_or_class->method_name( @args ). Sometimes that means that you don't care who the invocant is, like so:

sub inherited_util {
    my ( undef, @args ) = @_;
    ... 
}

or

sub inherited2 { 
    shift;
    ...
}

However, if you want to use utilities defined in the base module and inherit from the class behavior defined in that module, then that's exactly what the two use statements indicate.

Still, if you have two different types of behavior you want to use in modules, it's probably better to split the utility type things off into their own module, and use it from both modules. Either way, explicit behavior is often better than implicit.

However, I have used this pattern before:

sub import { 
    shift;
    my ( $inherit_flag ) = @_;
    my $inherit 
        = lc( $inherit_flag ) ne 'inherit' ? 0
        : shift() && !!shift()             ? 1
        :                                    0
        ;
    if ( $inherit ) { 
        no strict 'refs';
        push @{caller().'::ISA'}, __PACKAGE__;
        ...
    }
    ...
}

that way, I make one call with an explicit bundling of usages.

use UtilityParent inherit => 1, qw<normal args>;

Upvotes: 5

Related Questions