>Tried that. They are newly converted C programmers, and are unwilling to
>give them up.

There are scary things in here about prototypes as well.

--tom

------- Forwarded Message

From:         Tom Christiansen <[EMAIL PROTECTED]>
Subject:      Re: Conditionally use modules (use constant)
Cc:           [EMAIL PROTECTED], [EMAIL PROTECTED], [EMAIL PROTECTED]
Reply-to:     [EMAIL PROTECTED] (Tom Christiansen)
Organization: Perl Consulting and Training
Newsgroups:   comp.lang.perl.moderated
Delivery-Date: Wed Nov 17 19:07:00 MST 

     [courtesy cc of this posting also mailed to all cited authors]

In comp.lang.perl.moderated, [EMAIL PROTECTED] writes:
:Tye McQueen <[EMAIL PROTECTED]> wrote:
:>Bill Moseley <[EMAIL PROTECTED]> writes:
:>)     unless ( eval "use Apache::Constants qw/OK HTTP_NOT_MODIFIED/;" ) {
:>
:>If think your C<if( eval> will work in this case, but I find
:>checking C<$@> a more reliable way to check for C<eval> success. 
:
:Nope.  eval "use Whatever;" executes no run-time expressions
:whatsoever.  In my testing it returns undef in scalar context and an
:empty list in list context.  Thus the eval returns false whether or
:not the module exists.  Always check $@ instead, which is guaranteed
:to be '' on success and an error message on failure.

The first, and lesser, thing which I'd like to point out is a frequent
approach to detecting whether an exception occurred without needing to
consult $@, yet when the operation itself is not guaranteed to return
the truth status you would otherwise seek.  Watch:

    die unless eval "WHATEVER; 1";

Remember that eval() takes a block as it its argument, so you can put
more statements than just one there.  If the "WHATEVER" portion triggers
an exception, the "1" is never reached and a false value (undef, in fact)
is immediately returned.  If the "whatever" portion fails to generate an
exception, the eval() will return a true value (1, in fact) irrespective
of WHATEVER's return value.

I've used this in code like this:

    sub isa_valid_regex {
        my $re = shift;
        return eval { "" =~ /$re/; 1; };
    } 

The second--and by far the more important--concern than I wish to convey
to you is that by hiding the module from the compiler, which is 
essentially what the delayed loading will do, you lose out on any
semantic clues that the module, expecting to be loaded at compile-time,
could have given to the compiler.

Consider this simple module:

    package Alpha;
    use Exporter;
    @ISA = qw/Exporter/;
    @EXPORT = qw/FROB/;
    sub FROB() { 20 }
    1;

And here's another one, rather like it:

    package Beta;
    use Exporter;
    @ISA = qw/Exporter/;
    @EXPORT = qw/FROB/;
    sub FROB { -shift }
    1;

Now let's look at various incarnations of test code.

    # test1
    use Alpha;
    printf "I have %d socks.\n", FROB+5;

    # test2
    eval "use Alpha; 1" || die "can't load Alpha: $@";
    printf "I have %d socks.\n", FROB+5;

    # test3
    use Beta;
    printf "I have %d socks.\n", FROB+5;

    # test4
    eval "use Beta; 1" || die "can't load Beta: $@";
    printf "I have %d socks.\n", FROB+5;

    # test5
    $mod = shift;
    eval "use $mod; 1" || die "can't load $mod: $@";
    printf "I have %d socks.\n", FROB+5;

What happens when we run these?

    % perl test1
    I have 25 socks.

    % perl test2
    I have 5 socks.

    % perl test3
    I have -5 socks.

    % perl test4
    I have 5 socks.

    % perl test5 Alpha
    I have 5 socks.

    % perl test5 Beta
    I have 5 socks.

test1 and test3 are correct results for those modules, but the rest
certainly aren't.  Isn't that frighteningly strange?  What's going on
is that the poor compiler, whom you've been playing hide and seek with,
was deprived of its author-given right to see that FROB is a function.
Which means that in test2, test4, and both runs of test5, the token FROB
was compiled into a string, not a function call.  This is a situation
that the original code

    unless ( eval "use Apache::Constants qw/OK HTTP_NOT_MODIFIED/;" ) {

would be especially vulnerable to.  Why?  Because you'd do something
like 

    if ( $obj->field() == HTTP_NOT_MODIFIED )

but the compiler would count that as a string, and you'd get an invalid
numeric conversion, and numerically test against 0.

"Ahah!", you cry.  "You're not using -w and use strict.  Just do that
and your problem will be solved."

    % perl -Mstrict -w test2
    Bareword "FROB" not allowed while "strict subs" in use at test2 line 4.

Ok, not what do you do?  One thing you could do is use parentheses.
That would give you

    # test2
    eval "use Alpha; 1" || die "can't load Alpha: $@";
    printf "I have %d socks.\n", FROB(+5);

Let's run that:

    % perl -Mstrict -w test2
    I have 20 socks.

Huh?  Now it's producing an answer of 20, not the 25 that test1 correctly
produced.  Why's that?  Because you put the parens in the wrong place!
You really meant this:

    # test2
    eval "use Alpha; 1" || die "can't load Alpha: $@";
    printf "I have %d socks.\n", FROB()+5;

Which you'll find does finally spit out 25 socks, as test1 did.  So why
didn't the compiler help you here?  It had the prototype.  Well, no it
didn't.  You refused to let the poor compiler see the prototype in time.
And no, it didn't produce a single complaint about this, even with all
the stricts and -w flags and what-have-you.

What else can you do?  Well, you could pre-declare FROB.

    # test2
    sub FROB;
    eval "use Alpha; 1" || die "can't load Alpha: $@";
    printf "I have %d socks.\n", FROB+5;

When you try to run that, you get this:

    % perl -Mstrict -w test2
    Prototype mismatch: sub main::FROB vs () at /usr/local/perl/lib/Exporter.pm l
ine 56.
            Exporter::import('FROB') called at (eval 1) line 1
            main::BEGIN() called at /usr/local/perl/lib/Carp.pm line 1
            eval {...} called at /usr/local/perl/lib/Carp.pm line 1
            eval 'use Alpha; 1
    ;' called at test2 line 3
    I have 20 socks.

Notice that that is a warning only.  It still ran the program, and
the program still exited 0.  It's also a mandatory warning.  Watch:

    % perl test2
    Prototype mismatch: sub main::FROB vs () at /usr/local/perl/lib/Exporter.pm l
ine 56.
            Exporter::import('FROB') called at (eval 1) line 1
            main::BEGIN() called at /usr/local/perl/lib/Carp.pm line 1
            eval {...} called at /usr/local/perl/lib/Carp.pm line 1
            eval 'use Alpha; 1
    ;' called at test2 line 3
    I have 20 socks.

See?  Even without strict and -w, you get noise but no aborting.

So what are you to do?  Well, you could declare the prototype:

    # test2
    sub FROB ();
    eval "use Alpha; 1" || die "can't load Alpha: $@";
    printf "I have %d socks.\n", FROB+5;

And now you get the correct answer, 25, because the compiler not
only knows that FROB is a function; the compiler also realizes that
the function takes no arguments, and adjusts its parser accordingly.
Let me repeat that, more loudly this time: THE COMPILER REALIZES THAT
THE FUNCTION TAKES NO ARGUMENTS, AND ADJUSTS ITS PARSER ACCORDINGLY.
The very parse of the expression FROB+5 varies depending on whether or
not the compiler deems FROB a function, and if so, of what sort.

But you know something?  You shouldn't have to know that FROB is 
a function with a void prototype.  In fact, you aren't supposed
to know that stuff at all.  That's why we have the veil of abstraction
between the module's implementation and you.

It doesn't get any easier.  You'll find that Beta.pm has a rather
different definition for FROB than has Alpha.  Its signature is 
different.  That means you're going to have a really, *really*
unpleasant time trying to write test5 to behave properly.  Fact is,
there can exist no solution, since you can't know a priori what
$mod will contain in order to pre-declare what it's going to do.

This is just one of those places where it probably pays to let the
compiler do its job, and skip the eval completely.  

"But wait!", you say.  The whole goal was to use conditionally include
something at run time.  Without the eval(), the use() loads it at
compile time.

Then don't use use(), of course.  All use() is, is a compile-time require
plus a compile-time import.  So what you do is use a regular require,
and then you call everything in a fully qualified fashion.

    if ($something_wicked_this_way_comes) {
        require Carp;
        Carp::Confess("LASCIATE OGNI SPERANZA");
    } 

I don't think that you'll even find the autouse pragma of much service
here, at least in the general case.

    use autouse Apache::Constants => qw/OK() HTTP_NOT_MODIFIED()/;

might be good enough here, but that's only because you don't have
absolutely know idea what module is going to be loaded, the way
in test5 I didn't know.  Ok, that doesn't mean it's impossible.

    # test5
    BEGIN {
        $mod = shift;
        eval "use $mod; 1" || die "can't load $mod: $@";
    } 
    printf "I have %d socks.\n", FROB+5;

If I do that, then I can actually get it working properly.

    % perl ~/test5 Alpha
    I have 25 socks.

    % perl ~/test5 Beta
    I have -5 socks.

Why this finally works I leave as an exercise to the reader.  A second
question to consider is why you would virtually never want to do this.
The third and final question in this pop quiz is whether
    require $module;
and 
    eval "require $module; 1" || die;
differ, and whether you care.

In summary, I conclude that should you ever see 

    eval "use Module"

in a program, that you ought to be exceedingly cautious, because the
construct has so many subtle gotchas that even when you look closely,
and turn on all the cautions you can, something might still escape you.
I'm not quite ready to call this `eval "use Module" Consider Harmful',
but I'm definitely getting close to doing so. :-)

- --tom


------- End of Forwarded Message

Reply via email to