views:

106

answers:

4

Sometimes I need a useful utility function, like List::Util::max in the middle of a large program that does lots of stuff. So if I do

use List::Util 'max';

At the top of my program, I'm stuck with that symbol, polluting my whole namespace, even though I only need it in one subroutine.

So I've been thinking of trying a different pattern, instead:

use List::Util ();

# a whole bunch of stuff later...
sub blah { 
    List::Util->import( 'max' );
    $blah = max @foobar;
    ...
}

There are two problems with this, though. For one, it doesn't automatically unimport at the end of the block (drat.) I would have to undo everything with an unimport.

The other problem is that apparently prototypes don't get applied correctly, so I have to say max( @foobar ) instead of the prettier parenthesisless version.

Is there an easy way to temporarily import symbols for a block, which would automagically make them go away at the end of the block, and which would also handle prototypes correctly?

+4  A: 

Just do this, it's much better and cleaner:

package Foo;
use strict; use warnings;
use List::Util 'max';
use namespace::autoclean;

# your method definitions here...

namespace::autoclean will "unimport" the symbol after the package's compilation cycle is done. The call to it in your method will still work, but you have no namespace pollution (the *Foo::max symbol is removed) and calling $obj->max() will fail.

Alternatively, you might want to take a look at Lexical::Import (I know nothing about it; an irc birdie mentioned it).

Ether
While `namespace::autoclean` is spiffy, it doesn't solve the main problem which is that `max` is still there in the whole package instead of limited to a single block, which is what I'm really trying to achieve. I want my utility functions to behave like lexical variables.
friedo
@friedo: why is it a problem that `max` is available to the entire package? If the concern is namespace pollution, that's what this module takes care of.
Ether
Maybe I want a *different* `max` somewhere else in the package.
friedo
@friedo: I don't know anything about your context, but if I ran into that situation, I'd consider splitting my package into two.
Ether
True, that is the practical solution. But I'm just trying to see if there is something cooler than practical. :)
friedo
Ah well if it's for *coolness*, then let the discussion continue :D
Ether
+1  A: 

You can localize a symbol table entry:

use List::Util ();

@y = qw(1 3 5 -9 4);

sub max { # return maximum *absolute value* of list
    my $max = abs(shift);
    $max<abs($_) && ($max=$abs($_))  for @_;
    return $max;
}

sub max2 {
    local *max = *List::Util::max;
    return max(@_);
}

print "My max:         ", max(@y), "\n";    # ==> 9
print "List::Util::max ", max2(@y), "\n";   # ==> 5
mobrule
This will introduce subtle errors if either `max` has a prototype, since the effect of that is burned in at compile time. In `max2`, the prototype from `main::max` is used, not the one from `List::Util::max`. Helpfully, you should get a warning about the prototype mismatch on assignment.
Eric Strom
@Eric Strom - Good point, that would suck. Use a `local` subroutine name with caution.
mobrule
+1  A: 

perlfunc implies that no MODULE should do what you want:

sub blah {
    use List::Util qw(max);
    say max @foobar;
    no List::Util;
}

but that doesn't work -- at least not for List::Util. I believe that it would need to define an unimport method. Even then, I'm not sure if you could have a bare max in your module call different definitions.

Michael Carman
Wouldn't the `use` and `no` lines execute at a different (earlier) time than the rest of the sub definition?
Ether
Both `use` and `no` happen at compile time; they would be executed while `blah` was being compiled.
Michael Carman
+2  A: 

If you only use max in one subroutine, I wouldn't import it into the namespace at all. My solution is to

use List::Util;
sub blah {
    print List::Util::max(@list);
}
Mike
This still honours prototypes, so you can leave out the brackets: print List::Util::max @list;
snoopy