Andre_br has asked for the wisdom of the Perl Monks concerning the following question:
Hello my esteemed fellow monks
Iīve been coding Perl for a couple years already, but now I feel itīs time to convert my Perl4-style custom libraries into real .pm modules. The main reason is because Iīm implementing mod_perl and it seems Apache::Reload is only guaranteed to track updates on module-like 'libraries', called with use.
Iīve been through the manpages and also the Camel book, but the OO literature just feeks like Greek to me. Just canīt figure out how to do it! I get lost in the therminology real fast. So I wonder if you guys can give me and applied explanation, say, about how would be a module created after library that looks like this:
[mylibrary1.pl]
sub half {
my $number = shift;
return $number / 2;
}
The main issues are:
1) Ok, I know I have to save the file as .pm and declare first thing the name of the package package Mypackage;. This one is clear for me, ok.
2) Then I have to provide the list of functions I want to export, right?
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(half);
Ok, no problem with this one either.
3) Now is where I start to get lost. I don't know how to handle the new thing. I mean, there is something that shall be there, in order I can in the main say my $m = new Mymodule; in the main, right? I tried sub new {return bless {}; } but this feels so strange.
4) Then, with all set up, how shall I call the functions from the main? $m->half(10); ? This doesnīt work, since the output is Mymodule=HASH(0xbd5068) and not just 5! What am I doing wrong? Do I have to modify something in the half() function (and all of them), now thatīs a module-like library??
I really thank you if you can help me out on this. Sorry if this is too OO-newbie, but I think it may be an interessting material for the community, as I couldnīt find any didatic explanation on this library-module conversion over the web.
Thanks a lot!
Andre
Re: converting libraries into modules
by dragonchild (Archbishop) on Mar 05, 2006 at 02:30 UTC
|
In Perl5, libraries and classes are implemented using the same mechanism (packages, usually in their own .pm files), but they're completely different. If you're converting P4-style libraries to P5-style, all you need to do is follow this template.
Old:
[mylibrary1.pl]
sub half {
my $number = shift;
return $number / 2;
}
New:
[mylibrary1.pm]
package mylibrary1;
use base 'Exporter';
use vars qw( @EXPORT @EXPORT_OK );
@EXPORT = qw( half ); # These are the ones that are imported if nothin
+g is specified.
@EXPORT_OK = qw( half ); # These are the ones you may specify, if you
+want.
sub half {
my $number = shift;
return $number / 2;
}
1; # All .pm files must end with a true value
__END__
Now, when you use them, you'll do it like this:
Old way:
[myscript.pl]
require 'mylibrary1.pl';
print half( 10 ), "\n";
New way:
[myscript.pl]
use mylibrary1 qw( half );
print half( 10 ), "\n";
Now, the different between @EXPORT and @EXPORT_OK is how you construct the 'use' line. Here's a few examples:
use Foo; # Imports everything in @EXPORT, nothing in @EXPORT_OK
use Foo 'bar'; # Imports 'bar' if it's in @EXPORT_OK. Imports nothing
+from @EXPORT
use Foo (); # Imports NOTHING from either.
My criteria for good software:
- Does it work?
- Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
| [reply] [d/l] [select] |
|
although using Exporter is suggested by, say, h2xs, I think using Exporter isn't a good habbit.
Its not good to pollute namespace.
Using package::utilname(...); $utlobject->utl_method(...); is flexible enough and no real need to save few keystrokes by not pointing package name, and risking to have naming conflict later....
Also, extra dependency on Exporter, and many extra lines in your template to introduce Exporter, do not worth the effort.
Best regards,
Courage, the Cowardly Dog
| [reply] [d/l] [select] |
|
While I agree with you from a purity perspective, Exporter exists specifically to make the transition between P4 repositories and P5 libraries easier. In other words, Exporter is the exact right tool in this situation.
Now, if the OP wants, we can educate him on better software practices later. But, for now, this solves the OP's problem to a 't'.
My criteria for good software:
- Does it work?
- Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
| [reply] |
|
Re: converting libraries into modules
by rvosa (Curate) on Mar 05, 2006 at 10:26 UTC
|
You're almost there. Here's how to define the object and methods:
package MyModule;
# 'constructor'
sub new {
# if you call MyModule->new, $_[0] will be 'MyModule'
my $class = shift;
# $self can be any reference, but usually a hash
my $self = {};
# this is the 'magic' that creates the object
bless $self, $class;
return $self;
}
sub half {
# if you call $m->half(10), $_[0] will be the object
my ( $self, $number ) = @_;
# proceed as normal
return $number / 2;
}
package main;
# now no longer in package, but in script
# instantiate object, call constructor
my $m = MyModule->new;
# yields '5'
print $m->half(10);
What happened in your case was that you tried to divide the first argument in @_ in your 'half' sub by two. However, if you use 'half' as a method call (i.e. $m->half(10), instead of half(10)), the first argument is the $m object (sometimes called the 'invocant'), not the number 10.
Hence, the return value (being whatever you pulled off $_[0]) is the object, which, when printed out, yields Package=REFTYPE(address), in your case Mymodule=HASH(0xbd5068). Instead, you will want to divide the second argument, which is why, conventionally, people will copy from @_ like so: my ( $self, ... ) = @_;
Hope this helps. Good luck!
P.s. you don't need to use EXPORTER for OO modules, as the methods are attached to the objects (rather than having to be imported into the caller's namespace). | [reply] [d/l] [select] |
Re: converting libraries into modules
by Cody Pendant (Prior) on Mar 05, 2006 at 10:44 UTC
|
how shall I call the functions from the main? $m->half(10); ? This doesnīt work, since the output is Mymodule=HASH(0xbd5068) and not just 5! What am I doing wrong?
If you're doing it the $m->half() way, then you need to have a module that looks like this:
package MyPackage;
sub new {
my ($class) = shift();
my $self = {};
return bless $self, $class;
}
sub half {
my $self = shift();
my $number = shift();
return $number / 2;
}
1;
and a script that looks like this:
use MyPackage;
my $m = MyPackage->new();
print $m->half(10);
Where you create a new MyPackage object and then use the object->function call to get the result.
($_='kkvvttuu bbooppuuiiffss qqffssmm iibbddllffss')
=~y~b-v~a-z~s; print
| [reply] [d/l] [select] |
Re: converting libraries into modules
by Cody Pendant (Prior) on Mar 05, 2006 at 11:08 UTC
|
You can also write your functions so they work either way.
Say you create your module and export the half() function as before, you can have your function look like this:
sub half {
my $number;
if (ref($_[0]) eq 'MyPackage') {
my $self = shift();
$number = shift();
} else {
$number = shift();
}
return $number / 2;
}
so that, if called via $m->half(), it works the first way, but if called just via half() it works the second way.
($_='kkvvttuu bbooppuuiiffss qqffssmm iibbddllffss')
=~y~b-v~a-z~s; print
| [reply] [d/l] |
|
Ugh, please don't do that. It's brittle to maintain, hard to document, confusing to explain, and I've never seen a case where it really simplifies things.
If you really need to export a simple procedural interface, create an object and export curried functions that use the object to call the methods.
| [reply] |
|
| [reply] |
|
|
|
Great! Now I got it!
Thanks a lot for the replies, dragonchild, Courage, rvosa and Cody Pendant (great tip of yours, cody, this calling-aware approach!).
Iīm so glad to have you guys around, fellow monks!
Take care!
André
| [reply] |
|
Just to document an adaptation on Cody's approach, that Iīve just tested with success:
Instead of
sub half {
my $number;
if (ref($_[0]) eq 'MyPackage') {
my $self = shift();
$number = shift();
} else {
$number = shift();
}
return $number / 2;
}
We can make the adaptation of all perl4-ish functions possible just by adding a single line, on the top of each function's code:
sub half {
if (ref($_[0]) =~ /MyPackage/ ) { my $self = shift; }
# as the former Perl4-like functions donīt need the $self object, this
+ line's role is to clean up the first element of the @_ array! And, s
+o, make things ready for the shifts your code already is set to!
my $number = shift;
return $number / 2;
}
Detail: I call all my methods libraries MyPackage, Mypackage2 etc, so by testing with this regexp, I don't even have to worry to adapt this one-line according to the library Iīm converting into module! Just adding it all over the subs, and finally enter the OO world! | [reply] [d/l] [select] |
|
I don't think this approach really simplifies things (now you have two interfaces to explain), but if you do it this way I'd suggest you don't hardcode the class name into its methods. I think I'd do:
sub half {
my $number;
if (ref $_[0] eq __PACKAGE__) {
my ( $self, $number ) = @_;
}
# uncuddles elses are faster!
else {
$number = $_[0];
}
return $number / 2;
}
Or even:
sub half {
my $number;
if (ref $_[0]) {
my ( $self, $number ) = @_;
}
# uncuddles elses are faster!
else {
$number = $_[0];
}
return $number / 2;
}
And then, if you're one of those suspenders + belt (just to be safe, you know) kind of people, you can always do looks_like_number $number and throw an exception (with baroque stack trace) on false. | [reply] [d/l] [select] |
|
# uncuddles elses are faster!
What? Is that really true? I thought it was just a style choice...
| [reply] |
|
|
|