Question

I already asked a similar question, but it had more to do with using barewords for functions. Basically, I am refactoring some of my code as I have begun learning about "package" and OOP in perl. Since none of my modules used "package" I could just "require" them into my code, and I was able to use their variables and functions without any trouble. But now I have created a module that I will be using as a class for object manipulation, and I want to continue being able to use my functions in other modules in the same way. No amount of reading docs and tutorials has quite answered my question, so please, if someone can offer an explanation of how it works, and why, that would go a really long way, more than a "this is how you do it"-type answer.

Maybe this can illustrate the problem:

myfile.cgi:

require 'common.pm'
&func_c('hi');
print $done;

common.pm:

$done = "bye";
sub func_c {print @_;}

will print "hi" then "bye", as expected.

myfile_obj.cgi:

use common_obj;
&func_obj('hi');
&finish;

common_obj.pm:

package common_obj;
require 'common.pm';
sub func_obj {&func_c(@_);}
sub finish {print $done;}

gives "Undefined subroutine func_c ..."

I know (a bit) about namespaces and so on, but I don't know how to achieve the result I want (to get func_obj to be able to call func_c from common.pm) without having to modify common.pm (which might break a bunch of other modules and scripts that depend on it working the way it is). I know about use being called as a "require" in BEGIN along with its import().. but again, that would require modifying common.pm. Is what I want to accomplish even possible?

Was it helpful?

Solution 2

You can use common_obj::func_obj and common_obj::finish. You just need to add their namespaces and it will work. You don't need the '&' in this case.

When you used the package statement (in common_obj.pm) you changed the namespace for the ensuing functions. When you didn't (in common.pm) you included the functions in the same namespace (main or common_obj). I don't believe this has anything to do with use/require.

You should use Exporter. Change common_obj to add:

use base Exporter;
@EXPORT_OK = qw/func_obj finish/;

Then change myfile_obj:

use common_obj qw/func_obj finish/;

I am assuming you are just trying to add a new interface into an old "just works" module. I am sure this is fraught with problems but if it can be done this is one way to do it.

OTHER TIPS

You'll want to export the symbols from package common_obj (which isn't a class package as it stands).

You'll want to get acquainted with the Exporter module. There's an introduction in Modern Perl too (freely available book, but consider buying it too).

It's fairly simple to do - if you list functions in @EXPORT_OK then they can be made available to someone who uses your package. You can also group functions together into named groups via EXPORT_TAGS.

Start by just exporting a couple of functions, list those in your use statement and get the basics. It's easy enough then.

If your module was really object-oriented then you'd access the methods through an object reference $my_obj->some_method(123) so exporting isn't necessary. It's even possible for one package to offer both procedural/functional and object-oriented interfaces.

Your idea of wrapping old "unsafe" modules with something neater seems a sensible way to proceed by the way. Get things under control without breaking existing working code.


Edit : explanation.

If you require a piece of unqualified code then its definitions will end up in the requiring package (common_obj) but if you restrict code inside a package definition and then use it you need to explicitly export definitions.

It's very good that you're making the move to use packages as that will help you a lot in the future. To get there, i suggest that you start refactoring your old code as well. I can understand not wanting to have to touch any of the old cgi files, and agree with that choice for now. But you will need to edit some of your included modules to get where you want to be.

Using your example as a baseline the goal is to leave myfile.cgi and all files like it as they are without changes, but everything else is fair game.

Step 1 - Create a new package to contain the functions and variables in common.pm

common.pm needs to be a package, but you can't make it so without effecting your old code. The workaround for this is to create a completely new package to contain all the functions and variables in the old file. This is also a good opportunity to create a better naming convention for all of your current and to be created packages. I'm going to assume that maybe you don't have it named as common.pm, but regardless, you should pick a directory and name that bits your project. I'm going to randomly choose the name MyProject::Core for the functions and variables previously held in common.pm

package MyProject::Core;

@EXPORT = qw();
@EXPORT_OK = qw($done func_c);
%EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);

use strict;
use warnings;

our $done = "bye";

sub func_c {
    print @_, "\n";
}

1;

__END__

This new package should be placed in MyProject/Core.pm. You will need to include all variables and functions that you want exported in the EXPORT_OK list. Also, as a small note, I've added return characters to all of the print statements in your example just to make testing easier.

Secondly, edit your common.pm file to contain just the following:

use MyProject::Core qw(:all);

1;

__END__

Your myfile.cgi should work the same as it always does now. Confirm this before proceeding.

Next you can start creating your new packages that will rely on the functions and variables that used to be in the old common.pm. Your example common_obj.pm could be recoded to the following:

package common_obj;

use MyProject::Core qw($done func_c);

use base Exporter;
@EXPORT_OK = qw(func_obj finish);

use strict;
use warnings;

sub func_obj {func_c(@_);}
sub finish {print "$done\n";}

1;

__END__

Finally, myfile_obj.cgi is then recoded as the so:

use common_obj qw(func_obj finish);

use strict;
use warnings;

func_obj('hi');
finish();

1;

__END__

Now, I could've used @EXPORT instead of @EXPORT_OK to automatically export all the available functions and variables, but it's much better practice to only selectively import those functions you actually need. This method also makes your code more self-documenting, so someone looking at any file, can search to see where a particular function came from.

Hopefully, this helps you get on your way to better coding practices. It can take a long time to refactor old code, but it's definitely a worthwhile practice to constantly be updating ones skills and tools.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top