NAME
generics - pragma for adding generic parameters to modules
SYNOPSIS
use generics;
# for use from within a class
use generics params => qw(PARAMETER);
# another use from within a class
use generics default_params => (PARAMETER => "A value");
# using it from outside a class
# to change the classes parameters
use generics MyModule => (PARAMETER => "A new value");
# see DESCRIPTION below for a better understanding of this module
DESCRIPTION
Many languages incorporate the concept of generic programming, specifically C++ and Ada. The generics pragma was inspired by these languages, but because of Perl's existing type flexibility it does not need generic programming per say. Instead I took the idea of passing generic parameters into a pre-existing class as a form of class configuration. The same generic programming can be accomplished to a certain degree, although because generics parameters are assigned at compile time they become part of the class rather than the instance (as in most generics situations).
So all this said, at their heart, generics are class configuration parameters. They can get trickier depending on how you choose to use them. The easiest example is that of a "Session" object. Here is some sample code:
# be sure to load the
# object's module
use Session;
# set the generic params
use generics Session => (
SESSION_TIMEOUT => 30,
SESSION_ID_LENGTH => 20
);
# create a Session object instance
my $s = Session->new();
Generics are used here as a way of configuring the Session object to have a 30 minute timeout period and generate a session id that is 20 characters long. Any Session object you create after the use generics declaration will have those configuration parameters available to them.
The generics module is actually what is called a compiler pragma. Traditionally a pragma is kind of a suggestion made to the compiler so that it might perform some certain kind of optimizations on the code it is compiling. In Perl, pragmas are usually specialized modules that the compiler executes during the compilation process, and are more akin to macros.
While it would be just as simple to just add the parameters to the Session object constructor (new) and configure it each time, this way is cleaner and faster. It is cleaner, because you need not have to remember the parameters each time you create a new Session object (especially since they are unlikely to change throughout the life of your application). And it is faster because the use generics declaration will actually set the parameters during the compilation of the Session object, and not at run-time when you create the object.
The only drawback to the compilation time configuration is that once the module is compiled, those values are set. Of course this is not a drawback if you do not plan on changing the parameters, and want them to stay as they are through the life of your application. If your classes are designed well you will never have a need to change the parameters during runtime.
If however you do need to change things are runtime, there is a way. This method should only be used as a last resort however. Here is an example:
generics->change_params(Session => (
SESSION_TIMEOUT => 10,
SESSION_ID_LENGTH => 20
));
You do not have to re-use the generics module, as a matter of fact, if you do, you will get some weird results. This should only be used in extreme circumstances, when you have determined there is no other way that will work. It does not restore the default params either, it just changes the already existing ones.
There is also another side to generics. The side that lives within the actual class you are attempting to configure.
Here are 2 examples:
package Session;
use generics params => qw(
SESSION_TIMEOUT
SESSION_ID_LENGTH
);
In order for a class to be configured with generics, you must first specify what those parameters are in the class itself. The above statement does just that. The Session object will now only accept those two parameters, and throw an exception otherwise.
Here is the other example:
package Session;
use generics default_params => (
SESSION_TIMEOUT => 30,
SESSION_ID_LENGTH => 20
);
This example does just what the previous one does in terms of setting up the valid parameters for the Session object, with one difference. It assigns default parameters. Without the default parameters, the Session object would not work, and throw an exception at runtime. With the defaults, you can skip the use generics Session part and the class would just use the installed default values. Also, with defaults, you can choose to only set as many params as you need. Here is an example:
use generics Session => (SESSION_TIMEOUT => 120);
This code will utilize the default setting for the SESSION_ID_LENGTH param, but change the SESSION_TIMEOUT param to be 120 minutes.
Another important note is that you can make anything a generic parameter. The following bit of code is a valid use of generics:
use generics Session => (
SESSION_TIMEOUT => sub {
if (Date->now()->getDayOfWeek() eq "Wednesday") {
return 30;
}
else {
return 120;
}
},
SESSION_ID_LENGTH => 20
);
The above code uses an anonymous subroutine and the Date object (which can be found in the Utilities category in the iI::Framework) to get the day of the week and if it is Wednesday, it sets the session timeout to 30 minutes, otherwise it sets it to 120. This of course is kind of silly, but it just illustrates that you have alot of flexibility with generics. It is even possible to have an entire method of your class defined with generics if you are so inclined.
The parameters of the Session object can be used much like constants within the class code itself. So for example you might do something like this:
sub createSessionId {
my @chars = (a .. z, A .. Z, 0 .. 9);
return map { $chars[rand()] } (0 .. SESSION_ID_LENGTH);
}
Now whenever you call createSessionId you can be confident that it will return a string exactly as long as you specified (or not specified if the default_params was used).
METHODS
- change_params ($package, @generic_params)
-
If ever you need to change the module configuration you will need to re-import the the configuration. Here is a way to do that (without having to say import which wouldnt make as much sense semanticaly). Keep in mind though that this will not restore the default values originally assigned in the class, it will just overwrite the current ones.
This will be needed very rarely. If you find yourself using it you should question the reason first, and only use it as a last resort.
- has_generic_params ($package)
-
This method is a predicate, returning true (1) if the
$package
has generic parameters and false (0) otherwise. - dump_params ($package)
-
This will dump a hash of the generic parameters. One important thing to note is that it will execute the parameters, so this may not be very useful for subtroutine ref parameters.
BUGS
None that I am aware of. The code is pretty thoroughly tested (see "CODE COVERAGE" below) and is based on an (non-publicly released) module which I had used in production systems for about 2 years without incident. Of course, if you find a bug, let me know, and I will be sure to fix it.
CODE COVERAGE
I use Devel::Cover to test the code coverage of my tests, below is the Devel::Cover report on this module's test suite.
---------------------------- ------ ------ ------ ------ ------ ------ ------
File stmt branch cond sub pod time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
generics.pm 98.4 100.0 88.9 85.7 100.0 9.0 97.2
t/10_generics_test.t 100.0 n/a n/a 100.0 n/a 100.0 100.0
t/20_generics_inherit_test.t 100.0 n/a n/a n/a n/a 18.6 100.0
t/30_generics_errors_test.t 100.0 n/a n/a 100.0 n/a 71.6 100.0
t/test_lib/Base.pm 100.0 n/a 33.3 100.0 0.0 0.6 62.5
t/test_lib/Session.pm 100.0 n/a 33.3 100.0 n/a 1.9 81.8
---------------------------- ------ ------ ------ ------ ------ ------ ------
Total 99.2 100.0 66.7 94.7 66.7 100.0 95.6
---------------------------- ------ ------ ------ ------ ------ ------ ------
NOTE:
For some reason, Devel::Cover is giving me strange results. I think it might have to do with the nature of some of my tests. In the interest of honesty and full disclosure I reproduce the stats accurately here. But in fact the statement and subroutine coverage on generics.pm should be 100%, and conditional coverage should likely be that high as well (although I have to delve further to see for sure). This may have something to do with the fact that Devel::Cover is not seeing the three Broken modules at all (t/test_lib/Broken.pm, t/test_lib/BrokenTwo.pm, t/test_lib/BrokenThree.pm). It makes sense that Devel::Cover is not seeing them as they do not load correctly (this is part of the test suite to test errors), but I have found in the past that Devel::Cover sometimes has trouble reporting on things if it doesn't see all the modules.
SEE ALSO
Nothing I can think of yet. But this module was inspired by the 'constant' pragma, and the desire to assign those constants across module lines. It borrows some of its ideas from other languages, in particular Ada and C++/STL, although our generics are not instance oriented as theirs are.
AUTHOR
stevan little, <stevan@iinteractive.com>
COPYRIGHT AND LICENSE
Copyright 2004 by Infinity Interactive, Inc.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.