Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Dynamically Changing Packages w/out Eval

by Ovid (Cardinal)
on Jun 14, 2009 at 11:14 UTC ( [id://771388]=perlquestion: print w/replies, xml ) Need Help??

Ovid has asked for the wisdom of the Perl Monks concerning the following question:

I have some code which looks like this:

foreach my $block ($self->get_blocks) { my %data = map { $_ => $block->$_ } qw/ package code setup teardown /; eval <<" END"; package $data{package}; \$data{setup}->(); \$data{code}->(); \$data{teardown}->(); END $self->_croak($@) if $@; }

Frankly, I don't like the eval there because it obfuscates the code and breaks syntax highlighting. What I really want is something conceptually similar to this:

foreach my $block ($self->get_blocks) { package $block->package; eval { $block->setup->(); $block->code->(); $block->teardown->(); }; $self->_croak($@) if $@; }

That's much easier to read and it's regrettably a problem I face all of the time. Is there an easy way to force code to run in a different package when I don't know the package until runtime?

Replies are listed 'Best First'.
Re: Dynamically Changing Packages w/out Eval
by demerphq (Chancellor) on Jun 14, 2009 at 12:43 UTC

    Umm, what is it you think that the package statement does in this code?

    Because I'm hard pressed to think of anything that it might affect in the code you have posted. The vars are all lexical. So they arent modified at all by the package declaration.

    And actually your language is imprecise. Packages arent containers really. Not in the sense you state. Package statements are instead merely hints to the compiler as to how to deal with non-fully qualified identifiers. Subs dont run "in a package" they are compile time bound to specific lexical and global variables, which may be affected by the presence of a package statement, and indeed unqualfied sub declarations are installed in the stash relative to the current package, but other than that, well, packages dont exist. Not as a first order concept or object.

    So what *exactly* is the problem you are trying to solve? This has all the hallmarks of an XY question.

    update: after discussing on #p5p it was suggested that maybe you want to do this so as to not confuse Carp::carp or Carp::croak. If so then, well, those routines are arguably broken. Dont use them, possibly by changing them to cluck/confess and the problem goes away.

    ---
    $world=~s/war/peace/g

      Umm, what is it you think that the package statement does in this code?

      Because I'm hard pressed to think of anything that it might affect in the code you have posted. The vars are all lexical. So they arent modified at all by the package declaration.

      I know that. I'm not that much of a Perl newbie :)

      The exact problem I'm trying to solve is the same thing Test::Aggregate does: run arbitrary bits of code and ensure that we don't have namespace pollution. If one chunk of code defines a package variable or creates a subroutine in its namespace, I don't want that clashing with other chunks of code. Hence, the package declaration.

        Sorry ovid, but the code you posted as an example did not make that very clear*. You basically cannot get rid of namespace pollution in perl. It is impossible. Any piece of code can declare objects in any namespace at any time.

        As far as /useful/ advice, :-), take a look at the top of Benchmark.pm and see how Jarkko did it.

        * Update: i mean it didnt make your question clear. I know you arent a newbie. Remember we drank a fair amount of whiskey together in vienna? ;-)

        ---
        $world=~s/war/peace/g

        Perhaps copying and restoring the symbol table does what you want?

        use Data::Dumper; package Foo; $foo="bar"; sub quux { print "\$foo is '$foo'\n" } package main; my %Foo_save = %Foo::; print "-1-\n",Dumper \%Foo::; eval "\$Foo::bar = q{quux}"; print "-2-\n",Dumper \%Foo::; print "-3-\n",Dumper \%Foo_save; %Foo:: = %Foo_save; print "-4-\n",Dumper \%Foo::; Foo::quux(); eval "print \"but Foo::bar is '\$Foo::bar'\\n\""; __END__ -1- $VAR1 = { 'quux' => *Foo::quux, 'foo' => *Foo::foo }; -2- $VAR1 = { 'bar' => *Foo::bar, 'quux' => *Foo::quux, 'foo' => *Foo::foo }; -3- $VAR1 = { 'quux' => *Foo::quux, 'foo' => *Foo::foo }; -4- $VAR1 = { 'quux' => *Foo::quux, 'foo' => *Foo::foo }; $foo is 'bar' but Foo::bar is ''

        That fails if eval'ed code populates a scalar slot of an already present typeglob - if, for instance, the subroutine in Foo was named 'bar', then $Foo::bar would retain the value set in the string eval. Hmm.. maybe Clone would help?

Re: Dynamically Changing Packages w/out Eval
by Anonymous Monk on Jun 14, 2009 at 11:48 UTC
    I think this
    local *{$package}::__ANON__ = sub { ... };
      Thats the wrong syntax
      local *{$package."::__ANON__"} = sub { ... };
      Much easier to use Sub::Install
Re: Dynamically Changing Packages w/out Eval
by duelafn (Parson) on Jun 15, 2009 at 13:41 UTC

    Well, everything is possible with a source filter :) (Sorry, only solution I could think of.)

    package DynPackage; use Filter::Simple; use Text::Balanced qw/ extract_bracketed /; FILTER_ONLY code => sub { 1 while s/\bdyn_package \s+ ([^\n]+?) \s+ (\{.+)$ / do { my ($pkgcode, $stuff) = ($1,$2); my ($code, $rest) = extract_bracketed($stuff,'{}') +; qq| { \$DynPackage::pkg = $pkgcode; eval "package \$DynPackage::pkg;" . <<' DYNPACKAGE_AUTOBLOCK'; $code DYNPACKAGE_AUTOBLOCK } $rest |; } /xes }, # FOR DEBUGGING: # all => sub { print } ; 1;

    Later on...

    use DynPackage; foreach my $block ($self->get_blocks) { dyn_package $block->package { $block->setup->(); $block->code->(); $block->teardown->(); }; $self->_croak($@) if $@; }

    Good Day,
        Dean

Re: Dynamically Changing Packages w/out Eval
by betterworld (Curate) on Aug 24, 2009 at 12:15 UTC

    Some funny code I wrote yesterday (the technique can be probably used for your problem too):

    use Symbol qw(qualify_to_ref); use overload; my $stashref = qualify_to_ref("${namespace}::"); package tmp; local *tmp:: = *$stashref; overload->import('+' => sub { my $self = shift; my ($other, $info) = @_; # do some stuff });

    This sets up overloaded operators for the namespace with the computed name $namespace.

    Note that you cannot use "main" instead of "tmp", because the main namespace seems to be too read-only for this kind of hack (you'd get the error "Modification of read-only value attempted")

      A lot simpler and a lot less fragile:
      my @overload = ('+' => sub { my $self = shift; my ($other, $info) = @_; # do some stuff }); eval "package $namespace; use overload \@overload; 1" or die $@;
        I thought the original question was how to avoid eval :)
Re: Dynamically Changing Packages w/out Eval
by JavaFan (Canon) on Aug 24, 2009 at 20:51 UTC
    This doesn't avoid eval, but it shouldn't break syntax highlighting, nor postpone compile issues to runtime:
    sub run_in_package (&$) { my($code, $package) = @_; eval "package $package; &$code; 1" or die $@; } foreach my $block ($self->get_blocks) { run_in_package { $block->setup->(); $block->code->(); $block->teardown->(); } $block->package; }
      eval "package $package; &$code; 1" or die $@;

      I think you're missing a "\" before "$code". But then the code will be called via a code reference, and the contents of the code reference are still in the old package (where they were compiled):

      sub run_in_package (&$) { my($code, $package) = @_; eval "package $package; &\$code; 1" or die $@; } run_in_package { showcaller(); } 'foooo'; sub showcaller { warn scalar caller; }

      The text of the warning will be "main" (not "foooo").

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://771388]
Approved by moritz
Front-paged by Old_Gray_Bear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (5)
As of 2024-04-25 23:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found