PBP: String evaluations

Beatnik on 2006-09-05T18:25:14

In the process of rewriting some of my code according to Perl::Critics suggestions, I bumped into the 'String Evaluations' warning (page 161). I opened up the book and learned all kind of things from pages 161 to 164 but not really a lot about how to use eval instead.. mmmm.


String eval == source code generation != eval {

jjore on 2006-09-05T19:13:33

eval-block isn't the same thing as eval-string and you probably shouldn't think of them as related. The former is used when you're creating new source code on the fly and want to run it. The latter is when you want to trap die().

It's the same word but they aren't related. They share the variable $@/$EVAL_ERROR and both trap die() but that's incidental.

Replacements for string-eval are often dispatch tables, curried functions, closures, that kind of thing. The following is an example of something that can be written using some closures or by using string-eval. The BEGIN is just there to emphasise that this is a kind of static generation. You're free to come up with your own variations on this theme.

BEGIN {
    for my $name ( ... ) {
        no strict 'refs';
        *$name = sub {
            ...
        };
    }
}

vs

BEGIN {
    for my $name ( ... ) {
        eval <<"CODE";
            sub $name {
                ....
            }
CODE
    }
}

Re:String eval == source code generation != eval {

Beatnik on 2006-09-05T19:50:16

I do realize that :) Below is what I DO have.. I guess this is just one of those "perl can't parse perl properly" moments.
eval qq{
use Foo::$module;
\$value = Foo::${module}::bar(\$baz); };

Re:String eval == source code generation != eval {

runrig on 2006-09-05T20:47:53

Do you have a die $@ if $@; after that statement?

Re:String eval == source code generation != eval {

Beatnik on 2006-09-05T21:32:03

Yeah..

Re:String eval == source code generation != eval {

jjore on 2006-09-05T21:05:52

Ok, so just do "no strict 'refs'" for the parts that make sense to use it for. That's oodles nicer than string-eval for everything. I keep thinking there's a better use of Module::Pluggable or similar here but that usually implies ISA and class stuff which isn't what you coded.

# Validate that $module is safe...
 
no strict 'refs'; ## no critic (ProhibitNoStrict)
if ( not keys %{"Foo::${module}::"} ) {
# or
if ( not $INC{"Foo/$module.pm"} ) {
    ## no critic (ProhibitStringyEval)
    eval "require Foo::$module";
    die $@ if $@;
}
 
$value = &{"Foo::${module}::bar"}( $baz );

Re:String eval == source code generation != eval {

uri on 2006-09-06T05:48:57

there is even a simpler and safer (strict clean) way to do this. call the sub as a class method and you build the class name as a string. the sub should ignore its first arg which is the class.

$value = "Foo::${module}::bar"->( $baz );

and using eval to execute a dynamic require is a good idea. require with a class arg will do the right thing on different platforms. if you pass require a value, it will find that filename which isn't always portable. this is one of the few places i do eval on the fly because it has a major
win.

uri

Re:String eval == source code generation != eval {

Beatnik on 2006-09-06T08:10:45

So how do you catch errors without eval? Suppose bar in
$value = "Foo::${module}::bar"->( $baz );
fails or that Foo::${module}just does not exist?

Re:String eval == source code generation != eval {

uri on 2006-09-06T20:11:51

easy, just wrap that in a block eval. as was said earlier, eval BLOCK is very different than eval STRING.

also you could look in the symbol table for this entry and if it has a code ref. you can just check if the sub is defined too.

defined( &{"Foo::${module}::bar"} ) or die "blah" ;

that will need strict refs turned off for that line.

string eval is never needed just to do symbol table stuff. it is the one proper use of symrefs as they are meant to mung the symbol table.

uri