简体   繁体   中英

perl 'require' in begin block

I have the following code:

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

BEGIN {
       my $supported = undef;
       *compute_factorial = sub { if (eval { require bignum; bignum->import(); 1;}) {
                                    my $num       = shift;
                                    my $factorial = 1;
                                    foreach my $num (1..$num) {
                                        $factorial *= $num; 
                                    }
                                    return $factorial;
                                  }  else {
                                       undef;
                                     } };
       };

my $f = compute_factorial(25);
say $f;

I'm just testing something, not really a production code... I do have bignum pragma on my machine (perfectly loadable using use ), I was wondering why require doesn't work as it should be (I'm getting exponential numbers rather then "big numbers") in this case?

Thanks,

bignum's import needs to be called before compilation of the code it is intended to effect, or it doesn't work. Here, the BEGIN makes it called before your actual compute_factorial call, but not before the critical my $factorial = 1; is compiled.

A better approach for cases like this is just to directly use Math::Big*:

if (eval { require Math::BigInt }) {
    my $num = shift;
    my $factorial = Math::BigInt->new(1);
    foreach my $num (1..$num) {
        $factorial *= $num;                            
    }
    return $factorial;
} else {
    undef;
} 
BEGIN {
   require bignum;
   import bignum;
   my $x = 1;
}

and

require bignum;
import bignum;
my $x = 1;

are the same because require and import are executed after my $x = 1; is already compiled, so bignum never has a chance to make my $x = 1; compile into my $x = Math::BigInt->new(1); . Keep in mind that

use bignum;
my $x = 1;

is actually

BEGIN {
   require bignum;
   import bignum;
}
my $x = 1;

and not

BEGIN {
   require bignum;
   import bignum;
   my $x = 1;
}

The solution would be

BEGIN {
   my $sub;
   if (eval { require bignum; }) {
      $sub = eval(<<'__EOI__') or die $@;
         use bignum;
         sub {
            my ($num) = @_;
            my $factorial = 1;
            $factorial *= $_ for 2..$num;
            return $factorial;
         }
__EOI__
   } else {
      $sub = sub { croak "Unsupported" };
   }

   *factorial = $sub;
}

Of course, since you can simply eliminate the pragma, that would be best.

BEGIN {
   my $sub;
   if (eval { require Math::BigInt; }) {
      require Math::BigInt;
      $sub = sub {
         my ($num) = @_;
         my $factorial = Math::BigInt->new(1);
         $factorial *= $_ for 2..$num;
         return $factorial;
      };
   } else {
      $sub = sub { croak "Unsupported" };
   }

   *factorial = $sub;
}

As many other pragmas, in newer versions of Perl bignum is only active in scope where you imported it. However, unlike many it also does some funky messing up with upgrading scoped numbers that doesn't quite work with just require . You will have to break check for its existence and use in two different files to isolate scope and still let it do its magic.

big.pl

if (eval { require bignum; 1 }) {
    require big_loader;
}

print big_loader::big_num_returner();

print "still ok\n";

big_loader.pm

package big_loader;
use bignum;

sub big_num_returner {
    return 2**512
}

1;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM