[Chicago-talk] calling functions in a package by its name
Jonathan Rockway
jon at jrock.us
Wed Jan 16 12:07:29 PST 2008
On Wed, 2008-01-16 at 12:50 -0600, Matt Hucke wrote:
> Hi,
Hi!
>
> I have a project in which I make much use of package names that aren't known at compile-time, but
> are instead read from a config file. I'd like to be able to load modules, create objects, and call
> arbitrary functions in unknown packages without an object reference.
>
> Right now, I can do it by making more use of 'eval' than I'm comfortable with.
>
> Example package:
>
> package MysteryClass;
> use strict;
>
> our %secretStuff = ( # caller doesn't know this variable name
> text => 'this is config info about package ' . __PACKAGE__
> );
>
> sub getConfig # public API, implemented by lots of similar classes
> {
> return \%secretStuff;
> }
First off %secretStuff isn't secret, it's a package variable. That
means it's global and anyone can get at it with %
MysteryClass::secretStuff. "my" can be package-scoped; use that
instead. (Is using our necessarily bad? No. But my might be what you
were intending.)
>
> sub main
> {
> my $classname = "MysteryClass"; # in real life, read it from a config file.
>
> # Load a module identified only at runtime
> my $prog = "use $classname;";
> eval $prog; die("eval '$prog' failed with " . $@ ) if ($@);
You do have to use string eval to load the class. require is better
than use here, though. (Do you want $class->import to be called? Not
for classes, usually.)
>
> # call a static function from this module, without an object ref
> my $conf;
> $prog = '$conf = ' . $classname . '::getConfig();';
> eval $prog; die("eval '$prog' failed with " . $@ ) if ($@);
> print "config is: ". Dumper($conf);
Better to rewrite getConfig as:
sub getConfig { my $class = shift; return $class_data }
Then you can say:
my $config = $class->getConfig;
>
> # create an object
> my $fred;
> my $name = "Fred";
> $prog = '$fred=new ' . $classname . '(name => $name)';
> eval $prog; die("eval '$prog' failed with " . $@ ) if ($@);
> print "object is: ". Dumper($fred);
Indirect method call syntax is a terrible habit to get into. Never use
it. Let me show you some examples.
When you write:
my $foo = new Foo(args => 'go here');
it's hard to see what's going on. Let's try writing it with a direct
call:
my $foo = Foo->new(args => 'go here');
Clearer. Maybe we can do this?
my $class = 'Foo';
my $foo = $class->new(args => 'go here');
Yes, of course we can :) And now you don't need eval. This applies to
methods also:
my $class = 'Foo';
my $constructor = 'new';
my $foo = $class->$constructor(@args);
> }
>
>
> I have been wondering if there's a better way to do it. PerlCritic tells me that stringy eval is
> bad; a bit of googling tells me that it's considered harmful because it causes the interpreter to
> begin another parsing phase.
No, it's harmful because of:
my $class = "Foo; `rm -rf *`"
eval "require $class";
Or similar.
>
> Can anyone offer ways to call a function (known at compile-time) in a class known only at run-time,
> with neither an object reference or "eval"?
>
> Ideally, I'd also like to be able to do something like "isa" or "can" using just the class name
> (after use'ing it) - that way I can have some sanity checking of whatever class name the user put in
> the config file, rather than creating a wholly inappropriate object type and only finding out later
> that it won't fit.
How about:
my $class = 'Foo';
eval "require $class";
$class->can('new')->(); # Foo->new()
One more thing, you wrote:
exit(main());
sub main {
...
print "object is: ". Dumper($fred);
}
print returns 1 when it works, so main will usually return 1. This
means you exit with failure status no matter what. Potentially a bug.
> The project is a web framework that allows for object types to be specified in the config file:
Writing your own web framework, eh. I tried that once.
http://catalyst.perl.org/
http://www.packtpub.com/catalyst-perl-web-application/book
Finally, let me throw in a mention for things like
Module::Pluggable::Object or MooseX::Object::Pluggable for writing
plugins. Using a module is easier than doing it yourself, and they
shield you from all the string evals :)
Regards,
Jonathan Rockway
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: This is a digitally signed message part
Url : http://mail.pm.org/pipermail/chicago-talk/attachments/20080116/45af62a0/attachment.bin
More information about the Chicago-talk
mailing list