CGI/Perl Guide | Learning Center | Forums | Advertise | Login
Site Search: in

  Main Index MAIN
INDEX
Search Posts SEARCH
POSTS
Who's Online WHO'S
ONLINE
Log in LOG
IN

Home: Perl Programming Help: Intermediate:
Creating Modules

 



jeff
Deleted

Nov 17, 2000, 8:29 AM

Post #1 of 5 (401 views)
Creating Modules Can't Post

Hi everyone,

Say, I am looking for readable info on building and using modules. Most of the stuff including the exporter docs are a bit steep and I am sure what I intend to do can't be all in that depth.

I have several different programs that all work together. They all need to take the time in standard perl "time" format and have it converted to human readable time and date. This means that every program has the exact same convert subroutine written in. It would be really nice to have a one method module that took in the epoch time and spit out human time and thus make all the different scripts that much shorter.

Could anyone please direct me to a easier to understand way of doing this or briefly describe it to me?

Thanks much,

jeff


japhy
Enthusiast

Nov 17, 2000, 1:35 PM

Post #2 of 5 (401 views)
Re: Creating Modules [In reply to] Can't Post

I hate to inform you that your first foray into modules is for naught:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


$now = localtime;
# or
$now = localtime($seconds);
</pre><HR></BLOCKQUOTE>

------------------
Jeff "japhy" Pinyan -- accomplished author, consultant, hacker, and teacher



jeff
Deleted

Nov 17, 2000, 2:12 PM

Post #3 of 5 (401 views)
Re: Creating Modules [In reply to] Can't Post

Thank Japhy, I must have not written my needs well. It is more like

$now = (whatever time the file was written and could be november 22, 1972 13:22:22)

to

That file was written (whatever day, date, time, etc).

I need to take the epoch date and translate it into a human date with all the bells etc.

Example is the code I am using:

sub convert_time{

my $whattime=$_[0]; #get the epoch time that was passed to us
my ($sec,$min,$hour,$day,$mon,$year,$wday,$thedate)=();

$sec=(localtime $whattime)[0];
if ($sec<=9){$sec="0".$sec}; #if seconds < 10 add a leading zero
$min=(localtime $whattime)[1];
if ($min<=9){$min="0".$min}; #if minutes <10 add a leading zero
$hour=(localtime $whattime)[2];
if ($hour<=9){$hour="0".$hour}; #et.al ad.nausium
$day=(localtime $whattime)[3];
if ($day<=9){$day="0".$day};
$mon=(qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[(localtime $whattime)[4]];
$year=(localtime $whattime)[5]+1900; #add 1900 to get real year
$wday=(qw(Sun Mon Tue Wed Thu Fri Sat))[(localtime $whattime)[6]];
$thedate="$wday $mon $day, $year $hour:$min:$sec";
return $thedate;
}


Thanks for looking at it and helping me understand how to do this

Jeff


japhy
Enthusiast

Nov 17, 2000, 2:31 PM

Post #4 of 5 (401 views)
Re: Creating Modules [In reply to] Can't Post

I will, however discuss the basics of module writing, for you and anyone else interested.

I. MODULE BASICS

In Perl, modules store their functions and variables in their own little container of memory, traditionally called a "namespace" in most languages. With Perl, you create a namespace with the package declaration:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


# this file is Foo.pm
package Foo;
</pre><HR></BLOCKQUOTE>

From that point on, in the scope of that declaration, the variable $bar is really $Foo::bar, and the subroutine &quux is actually &Foo::quux. Package scopes are lexical. That means they exist only in the block that declares them:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


package A;
$x = 10; # $A::x = 10;
{
package B;
$x = 20; # $B::x = 20;
}
print $x; # 10
print $A::x; # 10
print $B::x; # 20

package B;
print $x; # 20
</pre><HR></BLOCKQUOTE>

package declarations can be changed by declaring another package.

It might help to know at this point that the default package is "main". This is the package you're in if you haven't changed anything. The namespace associated with the package "main" is written as "main::". All namespaces exist as a sub-namespace of main::. That means that Foo:: is the same as main::Foo::. Oddly enough, it's also the same as main::main::Foo::. A final interesting thing about "main" is that it can be left out of the main:: namespace, so $main::name and $::name are the same variable.

The filename of a module usually corresponds to the primary namespace in it. A namespace of Foo:: is usually in the file "Foo.pm". A namespace of Foo::Bar:: is probably in "Foo/Bar.pm".

Nested namespaces aren't related at all, except that they have part of their name in common. Nesting a namespace, though, is a good practice to indicate there is some functional relationship between the two. For instance, a namespace of Person:: might be for functions regular people do. You might also have a separate group of functions in the namespace Person::Baby::, for things only babies do. The namespace Person::Baby:: is created by a package declaration like this:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


package Person::Baby;
</pre><HR></BLOCKQUOTE>

Be sure not to declare the package as

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


package Person::Baby::; # don't do this!
</pre><HR></BLOCKQUOTE>

Because you will end up with variables that are very hard to get at (they'll have names like $Person::Baby::::bottle, which is quite irregular).

II. MODULE INTERFACE

Once you have a module written, with variables and functions and such, you want a way for your module to be usable. Let's assume our module is called "Cards.pm". It will have one function, &randcard, that will return (and remove) a random card from a deck.

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


# this file is Cards.pm
package Cards;

@ranks = ('Ace', 2..10, 'Jack', 'Queen', 'King');
@suits = qw( Clubs Diamonds Hearts Spades );
%seen = (); # whether we've picked the card already

sub randcard {
my ($r,$s); # rank and suit
while (1) {
$r = $ranks[rand @ranks];
$s = $suits[rand @suits];
$seen{"$r of $s"}++ ? last : next;
}
return "$r of $s";
}

1;
</pre><HR></BLOCKQUOTE>

There's the whole module. The 1 at the end is so that when we use or require() the module, it will return a true value, which Perl uses to determine successful loading. Now, to make this module a bit more safe, I'm going to turn on the 'strict' pragma. This means I must use lexically scoped variables (by using my()), use fully qualified variables (@Cards::ranks instead of @ranks), or declare package variables with the 'vars' pragma (or, as of Perl 5.6, using our()). So let's make the modifications:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


# this file is Cards.pm
package Cards;
use strict;
use vars qw( @ranks @suits %seen );

@ranks = ('Ace', 2..10, 'Jack', 'Queen', 'King');
@suits = qw( Clubs Diamonds Hearts Spades );
%seen = (); # whether we've picked the card already

sub randcard {
my ($r,$s); # rank and suit
while (1) {
$r = $ranks[rand @ranks];
$s = $suits[rand @suits];
$seen{"$r of $s"}++ ? last : next;
}
return "$r of $s";
}

1;
</pre><HR></BLOCKQUOTE>

The variables in the function are fine, since $r and $s are lexicals, and the %seen hash is declared previously.

Now, when we use this module, our code will look something like this:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


#!/usr/bin/perl -w

use Cards;
use strict; # because we're good programmers

for (1..10) { # ten random cards
my $card = Cards::randcard();
print "I picked the $card\n";
}
</pre><HR></BLOCKQUOTE>

Now, you'll notice that I had to refer to the &randcard function with a fully qualified name -- the function is not in the main:: namespace, it's in the Cards:: namespace, so I must access it as such.

When you use a module, two things happen (and they happen at compile-time, not run-time):

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


use Module;

# is the same as

BEGIN { # compile-time!
require Module; # execute module source
Module->import; # call its import() method
}
</pre><HR></BLOCKQUOTE>

If your module does not define an import method, that's fine -- Perl will just not do it. But it's that import method that allows you to export symbols (variables or functions) from the module, to the package that is using the module. To understand how aliasing works, you might want to read the Perl documentation in 'perldata'. Basically:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


# copying...
$Foo::bar = 10;
$copy_of_bar = $Foo::bar;
$copy_of_bar = 20;
print "$Foo::bar, $copy_of_bar"; # 10, 20

# compared with ALIASING
$Foo::bar = 10;
*copy_of_bar = \$Foo::bar;
$copy_of_bar = 20;
print "$Foo::bar, $copy_of_bar"; # 20, 20
</pre><HR></BLOCKQUOTE>

The syntax for aliasing a specific part of a symbol is:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


*alias = \$orig; # $alias IS $orig
*alias = \@orig; # @alias IS @orig
*alias = \%orig; # %alias IS %orig
*alias = \&orig; # &alias IS &orig
</pre><HR></BLOCKQUOTE>

To alias ALL of those at once, you use:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


*alias = *orig; # all of the above occur
</pre><HR></BLOCKQUOTE>

Now we're ready to write an import method for the module. We want to alias the function &randcard to the function &main::randcard. However, what if some package OTHER than "main" used the module? We can't just assume we were called by "main", so we find out what package called us using the caller() function.

Also, I'm changing the variables to lexical variables. The reason for this is, I don't want the user writing code to change the values in @Cards::ranks, or to clear the values in %Cards::seen.

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


# this file is Cards.pm
package Cards;
use strict;

my @ranks = ('Ace', 2..10, 'Jack', 'Queen', 'King');
my @suits = qw( Clubs Diamonds Hearts Spades );
my %seen = (); # whether we've picked the card already

sub import {
my $this_pkg = shift; # the first arg is 'Cards'
my $to_pkg = caller; # the package that used this module

# following is the ONLY USE OF SYMBOLIC REFERENCES
# this is pretty much the ONLY thing they're good for
# we have to turn off strict 'refs' to do it
no strict 'refs';
*{ $to_pkg . "::randcard" } = \&randcard;
}


sub randcard {
my ($r,$s); # rank and suit
while (1) {
$r = $ranks[rand @ranks];
$s = $suits[rand @suits];
$seen{"$r of $s"}++ ? last : next;
}
return "$r of $s";
}

1;
</pre><HR></BLOCKQUOTE>

That wasn't so bad. Now we can do:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


#!/usr/bin/perl -w

use Cards;
use strict;

for (1..10) {
my $card = randcard();
print "I picked the $card\n";
}
</pre><HR></BLOCKQUOTE>

Yay! Now, in case you didn't totally grok (that's a word from "Stranger in a Strange Land" -- it means more than "understand"... it means more like "to be one with") the symbolic reference I did, here it is again:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


*{ $to_pkg . "::randcard" } = \&randcard;
</pre><HR></BLOCKQUOTE>

On the LEFT side, I construct a glob like *main::randcard or *other::randcard. However, I do this with strings, and the 'refs' part of the 'strict' pragma does not allow that -- they're symbolic references. So I turn that part off for the rest of the function, and we're fine.

III. AUTOMATIC INTERFACE WITH EXPORTER

Rather than write your own import method for all your modules, you can use the standard Exporter.pm module and tell Perl to look for the function there. This requires a couple more variables to ease the transition:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


package Cards;
use strict;
use vars qw( @ISA @EXPORT );
require Exporter;

@ISA = 'Exporter'; # this is where 'import' is
@EXPORT = qw( randcard );

sub randcard {
my ($r,$s); # rank and suit
while (1) {
$r = $ranks[rand @ranks];
$s = $suits[rand @suits];
$seen{"$r of $s"}++ ? last : next;
}
return "$r of $s";
}

1;
</pre><HR></BLOCKQUOTE>

Now when we use the module, the &randcard function will be aliased automatically for us. If we want the user to manually request it -- that is, have to write code like:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


use Cards 'randcard'; # request it
</pre><HR></BLOCKQUOTE>

Then we use the @EXPORT_OK array instead. The @EXPORT array contains the names of functions and variables to import automatically, if nothing was requested. The @EXPORT_OK array holds ones that can be specifically requested. If you specifically request a symbol, then the symbols in @EXPORT won't automatically be aliased as well.

These have to be package variables -- you can't my them -- because Exporter.pm has to access them from its own package. So be sure, if you're using strict (like all good boys and girls) that you predeclare the arrays, or use full names for them.

(The same goes for @ISA -- it has to be a package variable, but that's Perl's business, and has nothing to do with the Exporter.pm module.)

IV. WHAT NEXT?

That's about it for now. Damn, this should be an article...

Anyway, for more help, read the 'perlmod' documentation, and the documentation for the Exporter.pm module.

All documentation is accessible through the 'perldoc' program:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


japhy% perldoc Exporter

japhy% perldoc perldata
</pre><HR></BLOCKQUOTE>

And you can also find the documentation online, at http://www.perldoc.com/ .

------------------
Jeff "japhy" Pinyan -- accomplished author, consultant, hacker, and teacher



japhy
Enthusiast

Nov 17, 2000, 6:04 PM

Post #5 of 5 (401 views)
Re: Creating Modules [In reply to] Can't Post

Ok, Jeff, I'm going to help you clean up the function a bit, and then hopefully, given the lecture I just gave on modules, you'll be able to put it in a module of its own.

(NOTE: you don't need to use a module for this, you could just stick it in a file like "functions.pl", and then require() the file at the beginning of your programs.)

Here's your original function:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


sub convert_time{
my $whattime = $_[0];
my ($sec, $min, $hour, $day, $mon, $year, $wday, $thedate) = ();
$sec = (localtime $whattime)[0];
if ($sec <= 9) { $sec = "0".$sec };
$min = (localtime $whattime)[1];
if ($min <= 9) { $min = "0".$min };
$hour = (localtime $whattime)[2];
if ($hour <= 9) { $hour ="0".$hour };
$day = (localtime $whattime)[3];
if ($day <= 9) { $day = "0".$day };
$mon = (qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ))[(localtime $whattime)[4]];
$year = (localtime $whattime)[5] + 1900;
$wday = (qw( Sun Mon Tue Wed Thu Fri Sat ))[(localtime $whattime)[6]];
$thedate = "$wday $mon $day, $year $hour:$min:$sec";
return $thedate;
}
</pre><HR></BLOCKQUOTE>

Ways to make this code run faster:

* call localtime() ONCE!
* do digit formatting with sprintf()
* build the lists of strings ONCE!

Here's an example:

<BLOCKQUOTE><font size="1" face="Arial,Helvetica,sans serif">code:</font><HR>


{
my @dow = qw( Sun Mon Tue Wed Thu Fri Sat );
my @mon = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
sub convert_time {
my ($s,$m,$h,$day,$mon,$year,$wday) = localtime shift;
return sprintf "%s %s %02d, %4d %02d:%02d:%02d",
$dow[$wday], $mon[$mon], $day, 1900+$year, $h, $m, $s;
}
}
</pre><HR></BLOCKQUOTE>

I declare the @dow and @mon arrays ONCE, and they're only visible to this function. And I called localtime() once, and I used sprintf() -- a very useful function, indeed, borrowed from C -- for the string formatting.

You could, however, use the POSIX module, and call the strftime() function to have this done almost automatically.

It's up to you to make this function into a module. ;)

------------------
Jeff "japhy" Pinyan -- accomplished author, consultant, hacker, and teacher

[This message has been edited by japhy (edited 11-17-2000).]

 
 


Search for (options) Powered by Gossamer Forum v.1.2.0

Web Applications & Managed Hosting Powered by Gossamer Threads
Visit our Mailing List Archives