Re: DWIM: autoloading classes
by liz (Monsignor) on Sep 17, 2003 at 09:14 UTC
|
Paranoid as I am, I am wondering whether the automatic loading of Foo::* modules isn't opening some serious security holes, because you can never know whether someone hasn't put a "Foo::Takeovereverything" module somewhere in @INC (especially with "." usually being part of @INC). Of course, this could be considered a feature as well.
Personally, I like the following idiom better for object oriented modules, as it makes it easier to use a lot of modules _and_ still keep a better overview:
use Foo qw(Bar Baz);
which would be equivalent to:
use Foo ();
use Foo::Bar ();
use Foo::Baz ();
Foo::import would look something like this:
my @module = qw(Bar Baz); # allowable modules
my %module = map {$_ => 1} @module; # easy lookup of modules
sub import {
my $class = shift;
return if $class ne 'Foo'; # allow import to be inherited by submo
+dules
my @require; # list of modules to be required
if (@_) {
foreach (@_) {
if ($_ eq ':all') {
@require = @module;
} elsif ($module{$_}) {
push @require,$_;
} else {
warn "Don't know how to load Foo::$_\n";
}
}
} else {
@require = @module;
}
foreach (@require) {
next if eval "require Foo::$_;"; # string eval for easiness
warn "Foo::$_: $@"; # could be die also if you prefer
}
} #import
With apologies to those for whom this would be bleedingly obvious.
Liz | [reply] [Watch: Dir/Any] [d/l] [select] |
|
While I'm too tired to implement it, one should be able to add import functionality to the above code so that symbols exported by Foo::Bar via "use Foo 'Bar';" are exported to the calling namespace. That should be doable by testing whether Foo::Bar uses Exporter to handle import() and friends and use the export_to_level() routine from Exporter. If the module uses its own import routine, one might... well... do evil things with eval and package.
| [reply] [Watch: Dir/Any] |
|
Not that I want to export anything: I do everything OO and nothing gets exported in my modules. If there are any global flags that would need to be set, I use class method accessors for them.
But anyway, but I fail to see what more evil things you could do with an exporter that you couldn't already do.
So what ev(i|a)l things exactly are you referring to?
Liz
| [reply] [Watch: Dir/Any] |
|
Re: DWIM: autoloading classes
by Abigail-II (Bishop) on Sep 17, 2003 at 01:29 UTC
|
If you are not exporting, why not create a file Foo.pm
package Foo;
use Foo::Dates;
use Foo::Customer;
use Foo::Order;
use Foo::Company;
use Foo::Company::SalesReps;
use Foo::Extremely::Long::Package::Name;
and use use Foo.
And if you want to take care of exporting, you could do
something like (untested):
package Foo;
use Foo::Dates ();
use Foo::Customer ();
use Foo::Order ();
use Foo::Company ();
use Foo::Company::SalesReps ();
use Foo::Extremely::Long::Package::Name ();
sub import {
Foo::Dates -> export_to_level (2, @_);
Foo::Customer -> export_to_level (2, @_);
Foo::Order -> export_to_level (2, @_);
...
}
Abigail
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
I can see from my original node that I (once again) was not terribly clear. The intent of this idea was to ensure that I had a general purpose module that I could point to any namespace and it would recursively travel through the directories (via File::Find or File::Find::Rule) and prepare those subroutines as I outlined above. Thus, I wouldn't have to change code in any of the classes found.
Further, by just adopting the syntax you listed in Re: The costs of packages, I think I might get exporting to work, also. What follows is a mish-mash of pseudo-code and Perl:
package Class::WhenNeeded;
use File::Find;
sub import {
my ($class, $top_package, $no_top_level) = @_;
my @dirs = map { find_top_level_dir($_, $top_package) } @INC;
foreach my $dir (@dirs) {
find({
wanted => \&create_package_sub($no_top_level),
preprocess => \&perl_packages,
}, $dir);
}
}
sub create_package_sub {
my $strip_top_level = shift;
my $module = make_module_name($File::Find::name, $strip_top_
+level);
my $call_pack = (caller(1))[1];
if (defined &{$module}) {
require Carp;
Carp::croak "Function $module already defined";
}
*{$module} = sub {
undef *{$module};
eval <<" END_EVAL";
package $call_pack;
use $module; # export goes to calling package?
END_EVAL
if ($@) {
require Carp;
Carp::croak "Could not load module: $module";
}
return $module;
}
}
Hopefully that clears things up.
Cheers,
Ovid
New address of my CGI Course. | [reply] [Watch: Dir/Any] [d/l] |
|
Your aiming at a somewhat different goal to me, in that my package don't exist as files and only come into being when they are used, but I really like where your going too.
I'm not sure if it fits with your intent, but I really like the idea of
use WhenNeeded qw[
HTTP::*
HTML::*
];
my $headers = new HTTP::Headers;
...
my $request = new HTTP::Request;
...
my $content = decode( $content );
But maybe that syntax is just too Javan :)
Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
If I understand your problem, I can solve it! Of course, the same can be said for you.
| [reply] [Watch: Dir/Any] [d/l] |
|
|
|
|
•Re: DWIM: autoloading classes
by merlyn (Sage) on Sep 17, 2003 at 02:02 UTC
|
| [reply] [Watch: Dir/Any] |
|
Actually, I didn't know about that module, but what I was looking at is still different. Even the Class::Autouse is considerably different:
use Class::Autouse qw{CGI Data::Manip This::That};
Mine finds all classes below a top level namespace. For example, if you have Foo::Bar, Foo::Bar::One::More, Foo::Bar::Two::Bad and Foo::Bar::Three::SaCharm, you could do make all of them "autoused" with this:
use Class::WhenNeeded 'Foo::Bar';
my $bad_luck = Foo::Bar::Two::Bad->new;
# or possibly
use Class::WhenNeeded 'Foo::Bar' => 'no_top_level';
my $good_luck = Three::SaCharm->new;
Perhaps my response to Abigail-II will clear it up. (Or perhaps it won't :)
Cheers,
Ovid
New address of my CGI Course. | [reply] [Watch: Dir/Any] [d/l] [select] |
|
It looks like Class::Autouse has a "superloader" option that will load any class as soon as you call a method on it.
| [reply] [Watch: Dir/Any] |
|
Re: DWIM: autoloading classes
by tsee (Curate) on Sep 17, 2003 at 09:35 UTC
|
Does this weird piece of code do what you want?
(Needless note to others: Don't use this code. Note to self: Don't write such code!)
package StupidAutoloader;
use strict;
use warnings;
use Carp;
our %Classes;
sub import {
my $class = shift;
my @hierarchies = @_;
%Classes = map {($_, undef)} @hierarchies;
*UNIVERSAL::new = sub {
my $proto = $_[0];
my $class = ref($proto) || $proto;
croak "Could not find method new() in package '$class'."
if ref($proto) or
exists $INC{$class} or
not in_hierarchy(\%Classes, $class);
eval "use $class;";
croak "Error loading module '$class': $@" if $@;
no strict 'refs';
goto &{"${class}::new"};
};
}
sub in_hierarchy {
my $h = shift;
my $c = shift;
return 1 if exists $h->{$c};
my @levels = split /::/, $c;
my $accum;
foreach (@levels) {
$accum .= $_;
return 1 if exists $h->{"${accum}::*"}
}
return 0;
}
1;
---
#!/usr/bin/perl
use strict;
use warnings;
use StupidAutoloader 'Data::*';
my $d = Data::Dumper->new([]);
print "$d\n";
my $g = Data::Grouper->new(a => []);
print "$g\n";
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
That's a very interesting piece of code. It never ceases to amaze what sort of things we can do with Perl. The only problem is that not all constructors are named 'new' :)
Cheers,
Ovid
New address of my CGI Course.
| [reply] [Watch: Dir/Any] |
|
That problem is alleviated by fiendishly abusing AUTOLOAD in UNIVERSAL. (There are people who'd kill for this... if you use it in production!)
---
#!/usr/bin/perl
use strict;
use warnings;
use StupidAutoloader qw/Math::* Data::*/;
my $function = Math::Symbolic->parse_from_string('1/2*m*v^2');
print $function->simplify(), "\n";
# Doesn't work because Math::Symbolic::Operator *implicitly requires o
+ther
# modules in the Math::Symbolic:: hierarchy:
# my $op = Math::Symbolic::Operator->new(...);
my $cmplx = Math::Complex->new(3, 2);
print $cmplx;
print Data::Dumper->Dump([$function]);
# Even this works:
use StupidAutoloader '*';
my $cgi = CGI->new();
print $cgi->header();
module:
package StupidAutoloader;
use strict;
use warnings;
use Carp;
our %Classes;
our $AUTOLOAD;
*UNIVERSAL::AUTOLOAD = sub {
my $proto = $_[0];
my $class = ref($proto) || $proto;
my $method_name = $AUTOLOAD;
die "Could not determine method name."
if not defined $method_name;
$method_name =~ /::(\w+)$/ or die;
$method_name = $1;
croak "Could not find method new() in package '$class'."
if ref($proto) or
exists $INC{$class} or
(not _in_hierarchy(\%Classes, $class) and not exists $Classes
+{'*'});
eval "use $class;";
croak "Error loading module '$class': $@" if $@;
no strict 'refs';
goto &{"${class}::${method_name}"};
};
sub import {
my $class = shift;
my @hierarchies = @_;
foreach (@hierarchies) {
$Classes{$_} = undef;
}
}
sub unimport {
my $class = shift;
my @hierarchies = @_;
if (not @hierarchies) {
%Classes = ();
}
foreach (@hierarchies) {
delete $Classes{$_};
}
}
sub _in_hierarchy {
my $h = shift;
my $c = shift;
return 1 if exists $h->{$c};
my @levels = split /::/, $c;
my $accum;
foreach (@levels) {
$accum .= $_;
return 1 if exists $h->{"${accum}::*"}
}
return 0;
}
1;
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: DWIM: autoloading classes
by adrianh (Chancellor) on Sep 17, 2003 at 07:30 UTC
|
use Foo::Dates;
use Foo::Customer;
use Foo::Order;
use Foo::Company;
use Foo::Company::SalesReps;
use Foo::Extremely::Long::Package::Name;
at the top of all my files I would be saying to myself "Gosh. We seem to have a lot of very tight coupling and duplicate code here. Probably need to do some refactoring" :-) | [reply] [Watch: Dir/Any] [d/l] |
Re: DWIM: autoloading classes
by IlyaM (Parson) on Sep 17, 2003 at 09:34 UTC
|
| [reply] [Watch: Dir/Any] |
Re: DWIM: autoloading classes
by idsfa (Vicar) on Sep 17, 2003 at 15:57 UTC
|
This subject seems to be a
recurring theme. The snippet referred to allows more complex selection mechanisms than just *.
| [reply] [Watch: Dir/Any] |
Re: DWIM: autoloading classes
by pdcawley (Hermit) on Sep 19, 2003 at 06:07 UTC
|
The problem with deferring loading 'til runtime is that modules that make use of, say CHECK or INIT blocks will fail to work. Admittedly, such modules are usually 'language warping' type tools, but you might be attempting to use them indirectly.
A better option might be to have an Application class that loads everything the app needs in one fell swoop, which also helps with module codependencies. For the purposes of testing, where you want to make sure that modules you believe to be isolated actually are isolated, you could write yourself an export routine, so you could do:
use Application qw/Foo::Bar Foo::Baz Foo::Wibble/;
# Testcode goes here
For evilness points you could have Application detect when it's being run via testing code and have it add any 'new' modules to its list of modules to load when used without a module list. However, I think throwing an exception in all cases would be a rather more sensible option.
Going down that track a little further, if we assume that the only time Application will be used with a module list is during testing (if you want to vary the list of modules for different scripts, subclass Application) then you could have it generate a list of tested modules, then have a 99sanitycheck.t that verifies that Application has been used to explicitly load every module that it will load by default.
Hmm... I think I might have to find the tuits to write this. | [reply] [Watch: Dir/Any] [d/l] [select] |
|
Agreed, run-time loading is a less than ideal solution for any number of modules (it fubars exports, in addition to the already mentioned CHECK and INIT issues). The whole reason that I was even thinking along those lines was because I was looking to avoid having to rewrite code in the main portion of a program every time a new submodule was added to extend the functionality.
This trick is really intended for loading all modules which fullfil a predetermined API, even if they were written long after the application which will use them. Strangely enough, I am actually using perl to write reports (in this case on instances of custom applications) and wanted to stop having to rewrite the damned thing every time we came out with a new product. Now I only have to write code which can gather the desired information from each new app and massage it into the common format.
I'd love to see some way to do this without losing the exports (Win32::TieRegistry's $Registry in particular). Dynamically loading OS-dependant modules would also be nice. Also a million dollars and a pony.
Remember, when you stare long into the abyss, you could have been home eating ice cream.
| [reply] [Watch: Dir/Any] |