Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

A simple import() for those special moments

by Aristotle (Chancellor)
on Jan 19, 2003 at 00:12 UTC ( [id://228094]=perlmeditation: print w/replies, xml ) Need Help??

Update: this approach is not viable unless you accept heavy caveats. See Re^2: A simple import() for those special moments

Update++: this approach is not viable at all - see Re^4: A simple import() for those special moments..

Occasionally I have a small module that is supposed to keep a small handful of symbols around, most of the time for use with utility scripts that are pretty short themselves. Basically, I don't care for the flexibility Exporter gives me.

Even when I want it, I find the module's interface involving settings up several globals and inheriting from Exporter just plain ugly.

After poking around for a while, I ended up with the following import:

package Foo; use vars qw($foo_var %foo_hash @foo_barr); sub foo_bar { 1 } my $exported = qr/^foo_/; sub import { my $caller = caller() . '::'; do { require Carp; Carp::croak("You cannot specify an import list to Foo"); } if @_ > 1; $main::{$caller}->{$_} = $Foo::{$_} for grep /$exported/, keys %Foo::; } 42;

It will simply export any symbol, regardless of its type (function, hash, array etc) whose name matches the pattern in $exported - in this case all the variables declared with vars and the foo_bar function. It doesn't even have to turn off strictures. :)

Is there any reason I should be wary of this?

Update: fixed logic and syntax error per sauoq's post.

Makeshifts last the longest.

Replies are listed 'Best First'.
Re: A simple import() for those special moments
by sauoq (Abbot) on Jan 19, 2003 at 00:45 UTC

    For the purposes you suggest, it would probably be just fine. You do have an error though; import() will be called with the package name (which is needed for inheritance but you probably don't care about it) so you'll need to shift it off of @_ or your sub will always die.

    You also have a small syntax error on your last for line. (No ending paren.)

    -sauoq
    "My two cents aren't worth a dime.";
    
      File to post transition problems - I was shifting off the package name to a variable in the code, but it was a remainder from previous attempts I got rid off before posting. Of course, I forgot to change the if accordingly.. I also wasn't using the modifier form of for, that's where the superfluous opening paren (not a missing ending paren ;)) came from.

      Makeshifts last the longest.

Re: A simple import() for those special moments
by MarkM (Curate) on Jan 19, 2003 at 06:14 UTC

    For some modules, I have come to the conlusion that nothing more than the following is necessary:

    sub import { my $caller = caller; *{$caller . '::FUNCTION1'} = \&FUNCTION1; *{$caller . '::SCALAR1'} = \$SCALAR1; }

    I usually end up with code such as the above for very specialized modules that need to load quickly, without any dependencies. Exporter.pm, even split into Exporter.pm and Exporter/Heavy.pm, is beastly long for the most common usages.

    Some benefits of this approach include the ability to rename objects, and the ability to export lexical objects (in the above case, $SCALAR1 could be declared with my()).

    I am not recommending my solution over yours. I am agreeing that rolling your own import() function can be very practical, and I believe that the practice suits the Perl mindset. Exporter.pm is one (convenient?) way to do it. It isn't the only way to do it.

Re: A simple import() for those special moments (bugfixes)
by ihb (Deacon) on Jan 20, 2003 at 12:20 UTC
    It will simply export any symbol, regardless of its type (function, hash, array etc) whose name matches the pattern in $exported - in this case all the variables declared with vars and the foo_bar function.

    One should perhaps also point out that variables declared with our() will be exported too. I've realized that some people are confused about what our():ed variables really are.

    But something really nasty can happen. It might export whole packages! Indeed, it's not very likely, but there's still a risk. Example:

    { package Foo::foo_bar; our $bar = 'BAR'; } use Foo; print $foo_bar::bar; # 'BAR'!

    There's also a bug in the code. If the caller is a nested package, e.g. A::B, then it won't work. This is due to $main::{$caller}->{$_}. The keys in %main:: (or %:: for short) that end in :: are just the first part of the package name. This behaviour nests, so A::B's (the ' is not a package delimiter here ;)) symbol table is found in %{$main::{'A::'}{'B::'}}.

    This being a copy-n-paste candidate make those hardcoded Foos hurt an eye of mine. I'd really like see __PACKAGE__ utilized here. Each time you copy this you'll (or someone else who rip this code) have to change those package names and that increases the risk of getting a bug.

    To the real question; if you should be wary of this. Unless you've made it very general, i.e. works for all callers and packages, I would. Unless you're totally sure there's no bug, I would. There will be quite a few modules to patch after a year or two if you just copy it.

    Below is my version, with minimal changes from the original:

    sub import { my $caller = caller() . '::'; do { require Carp; Carp::croak("You cannot specify an import list to " . __PA +CKAGE__); } if @_ > 1; no strict 'refs'; *{"$caller$_"} = *{__PACKAGE__ . "::$_"} for grep !/::$/ && /$exported/, keys %{__PACKAGE__ . '::'} +; }

    I don't claim this version to be bug free either. :)

    Btw, how about making this into a module and calling it Exporter::Pattern? (Or Exporter::Regex(p), but I like Exporter::Pattern better.) The interface would be quite simple:   use Exporter::Pattern qr/PATTERN/;

    ihb

      Thanks for the constructive comments - that's the kind of reply I was hoping for.

      All points accepted. I tried to change it with your arguments in mind and noticed an obvious, major caveat along the way I hadn't picked up on before because I still don't fully grok typeglobs: it exports the entire typeglob - lock, stock and barrel. If an importing package already has a $foo_bar, this routine will overwrite the typeglob in the process of exporting &foo_bar, even if it never exports a $foo_bar of its own.

      Oops.

      That was not what I had in mind. So I pulled out my Camel and read up on typeglobs, the section that talks about the *sym{THING} syntax and tried grepping the list of slots of each glob. That works fine - except the SCALAR slot always contains a reference, whether such a package wide scalar has been named or not. In other words, if *x{SCALAR} is \undef, there is no way to know whether it wasn't mentioned at all (and should therefore not be exported) or it was mentioned in such as undef $x; or use vars qw($x); which define but do not assign a value to the variable.

      Which means this approach is not viable..

      Makeshifts last the longest.

        it exports the entire typeglob

        I thought that was intentional. :)

        If an importing package already has a $foo_bar, this routine will overwrite the typeglob in the process of exporting &foo_bar, even if it never exports a $foo_bar of its own.

        It's more likely to be the other way around, i.e.

        sub foo_var { 'a' } use Foo; foo_var(); # Undefined subroutine called.
        It's easy to check if a glob gets overwritten though.

        This isn't such a big issue as you seem to think. Problems will only occure when you import after defining your own data types. Special exception for subroutines though. They can be declared and that (including the prototype) can disappear. E.g.

        sub foo_var ($$); use Foo; foo_var;
        complains under strict because &foo_var isn't declared anymore when foo_var; is found.

        Anyway, usually modules are use()d before subroutines are defined, and subroutines are usually defined before variables. So it's not that bad. This works perfectly (albeit a bit dangerous):

        use Foo; sub foo_var { 1 } foo_var();

        Cheers,
        ihb
Re: A simple import() for those special moments
by Ctrl-z (Friar) on Jan 20, 2003 at 17:05 UTC
    Interesting. I had an old piece of code something similar, that I used for miscellaneous function libraries.
    It worked on the surface, but as ihb pointed out, theres probably subtleties i have not understood or accounted for - Globs are pretty scary!

    package Exporter::Lite; no strict 'refs'; sub import { my $namespace = shift; my $caller = caller(); if ($namespace eq __PACKAGE__) { # ie use Exporter::Lite; # put import symbol in callers table, and ignore any args *{ "$caller\::import" } = *{__PACKAGE__."\::import"}; return; } else { # ok, we have a "use myModule qw(blah foo);" foreach(@_) { *{ "$caller\::$_" } = *{"$namespace\::$_"}; } } } 1;
    edit: my way definitely suffers from the same clobbering problem as mentioned in Aristotle's last post. Just sneaked in before me.
    But I wonder, if your are explicitly importing a symbol, would you be likely to name your own variables the same as what had been imported?
Re: A simple import() for those special moments
by shotgunefx (Parson) on Jan 21, 2003 at 00:08 UTC
    When I want to lighten up, I use my slightly longer stab.

    -Lee

    "To be civilized is to deny one's nature."
      Nice, although the interface (several globals) is just like Exporter's. Checking $VERSION is something I hadn't thought about.

      Makeshifts last the longest.

        Thanks. It's easy enough to make them lexicals. It just didn't bother me to have them our variables. As far as matching Exporters semantics, I wanted it to. I wrote it when tilly's involvment in Exporter was questioned but it made a nice drop-in for existing modules that I wanted to lighten up for CGI purposes.

        -Lee

        "To be civilized is to deny one's nature."

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (6)
As of 2024-03-19 08:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found