Re: How not to hardcode a package name?
by moritz (Cardinal) on Aug 28, 2008 at 09:24 UTC
|
You can solve any problem in computer science by introducing another abstraction layer, or so they say.
package Array::Extract;
our $worker_pkg;
...
package Array::Extract::Worker;
BEGIN {
$Array::Extract::worker_pkg = __PACKAGE__
}
...
(Not tested).
That works even for separate files, and can be overridden by other modules.
Update: I choose BEGIN out of habit, maybe something later like CHECK or INIT might be better, but then again they might cause problems with mod_perl. | [reply] [d/l] [select] |
|
|
package Array::Extract;
our $worker_pkg;
...
package Array::Extract::Worker;
BEGIN {
$Array::Extract::worker_pkg = __PACKAGE__
}
I didn't know I could do that. Or better: I hadn't thought I could do that. Of course the nice point is that in the BEGIN block I don't need to specify the full package name of $worker_pkg, since our creates an alias to it in all the lexical scope surrounding it.
Similarly, I may still be using a regular lexical variable. I'm wondering if there are any possible drawbacks/differences/gotchas with one approach wrt the other.
Update: I choose BEGIN out of habit, maybe something later like CHECK or INIT might be better, but then again they might cause problems with mod_perl.
I must admit I know next to nothing about those special blocks, and I should probably read up something. Re mod_perl, I'm not a web programmer, but it's good to know anyway. Although I can't understand why: if it's valid Perl, it should continue to work in that environment too...
| [reply] [d/l] [select] |
|
|
Similarly, I may still be using a regular lexical variable. I'm wondering if there are any possible drawbacks/differences/gotchas with one approach wrt the other.
Lexical (my) variable: good encapsulation trough limited scope, but you're forced to keep those two packages in the same file. You can't override it from the outside, which can be both good and bad, depending on whether you favor security or extensibility.
For package variables it's just the other way round ;-)
Corion pointed out that this approach does "action at a distance", which you probably can't entirely avoid unless you do symbol table hackery. my variables limit the scope of the spooky action, though.
I must admit I know next to nothing about those special blocks, and I should probably read up something. Re mod_perl, I'm not a web programmer, but it's good to know anyway. Although I can't understand why: if it's valid Perl, it should continue to work in that environment too...
The various compilation and execution phases are a not-so-simple (but really powerful) beast. CHECK and INIT are not executed inside a string eval, which is documented in perlmod, and apparently (I'm not a mod_perl user either) what mod_perl does to execute its scripts and modules.
I don't know if that's a limitation in the implementation or a design goal, though.
| [reply] [d/l] [select] |
|
|
|
|
package Array::Extract;
my $worker_pkg;
my $priority = -1;
sub import {
my ($class, %args) = @_;
if (exists $args{priority}) {
# Register new implementation
if ($args{priority} > $priority) {
$worker_pkg = caller;
$priority = $args{priority};
}
} else {
warn "Using $worker_pkg, priority $priority\n";
$class->SUPER::import(ARRAY => $worker_pkg);
}
}
# Worker.pm
package Array::Extract::Worker;
use Array::Extract priority => 1;
# somewhere else
use Array::Extract::Worker; # has to be used first
use Array::Extract;
This avoids these global variables that have been described as "action at a distance". Using "priority", you can even have several implementations, and it will choose the one with the highest priority; but that's just an extra bonus, it'll work without priorities too. | [reply] [d/l] |
Re: How not to hardcode a package name?
by Corion (Patriarch) on Aug 28, 2008 at 09:19 UTC
|
$class->SUPER::import(ARRAY => __PACKAGE__.'::Work');
If you want to be more fancy/flexible, you could do it like this:
package Array::Extract;
my $worker_package = __PACKAGE__ . '::Work'; # or whatever other name
+you want
sub worker_extract {
... your code ...
};
{
no strict 'refs';
*{"$worker_package\::extract"} = \&worker_extract;
};
| [reply] [d/l] [select] |
|
|
A little convention can go a long way:
$class->SUPER::import(ARRAY => __PACKAGE__.'::Work');
I personally believe that it would still amount to "double hardcoding," albeit in a more "hyerarchical" (for what that it may mean in Perl 5) and thus clean way: what if I decide to change say "::Work" to "::Tool" later?
If you want to be more fancy/flexible, you could do it like this:
Well, this is just as ugly as working with symrefs generally is ;) and somehow unsatisfactory for leaving the same sub in two namespaces. (I presume that one could go just as far as deleting it from Array::Extract, but that would be kind of an exaggeration, I admit.) Or else I may actually adopt an anonymous sub to start with:
package Array::Extract;
my $worker_package = __PACKAGE__ . '::Work'; # or whatever other name
+you want
{
no strict 'refs';
*{"$worker_package\::extract"} = sub {
... your code ...
};
}
And that would be nearly the same thing in most cases: that is, unless that sub creates a closure, which after all may be what I want anyway...
The only problem I see with this approach is that instead of having a single sub like that, I may have quite a lot of them. (In which case I would probably put them into a hash and generate their package names programmatically...)
| [reply] [d/l] [select] |
|
|
sub worker {
my ($self, $subclass, $name, $code) = (undef,'Worker','work');
if( @_ == 2) {
($self,$code) = @_;
} elsif( @_ == 3) {
($self,$name,$code) = @_;
} elsif( @_ == 4 ) {
($self,$subclass,$name,$code) = @_;
};
my $n = "$self\::$subclass\::$name";
no strict 'refs';
*{$n} = $code;
};
__PACKAGE__->worker('Tool' => 'toolit' => sub {
...
});
__PACKAGE__->worker(sub {
...
});
| [reply] [d/l] |
Re: How not to hardcode a package name?
by JavaFan (Canon) on Aug 28, 2008 at 10:09 UTC
|
Personally, I don't have much problems with "hardcoded" package name, but, looking at your code fragment, I wonder, "why have another class anyway?". Wouldn't the code just work if you remove the line "package Array::Extract::Work;" and use
$class->SUPER::import(ARRAY => __PACKAGE__);
| [reply] [d/l] |
|
|
As I wrote, one reason is that I my code is modeled on a fragment from autobox's docs. But then as I also wrote, this is just one possible situation in which a similar case of (what I call) "double hardcoding" may happen. More seriously, the additional class is indeed a worker class, and is just there to keep things separated and clean: in a more realistic example, Array::Extract may have its own subs and methods, and I rightly want to define separately the methods that will work on autoboxed objects.
| [reply] [d/l] [select] |
|
|
| [reply] |
Re: How not to hardcode a package name?
by JadeNB (Chaplain) on Aug 28, 2008 at 20:00 UTC
|
I can't count the number of times that this question has bugged me in my own coding! It seems that there should be some CPAN module that does this, analogous to UNIVERSAL::require, which lets you require a module whose name is in a variable.
While I know that it wouldn't be elegant, isn't there an eval-based way to do this? I tried the simple-minded my $a = 'Package'; eval "package $a";,
but it doesn't work (I suspect because I don't really understand the eval EXPR form). | [reply] [d/l] [select] |
|
|
The package of a line of code is set when that line is compiled.
Furthermore, package is lexically scoped, so you can't just put the eval in a BEGIN.
You can do
my $p = 'Package';
my $c = 'print(__PACKAGE__);'; # Some code
eval "package $a; $c";
| [reply] [d/l] [select] |
|
|
| [reply] [d/l] [select] |
|
|
|
|
I personally believe that you could use the "code in @INC" feature I mentioned in the very root node of this thread. But then it wouldn't be suitable to only work out a package statement, since it is precisely designed to deal with full modules...
Some (old) posts of mine in which I dealt with the technique:
| [reply] [d/l] [select] |