[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