Export by attribute, can you make it work?

schwern on 2008-01-25T00:41:25

I was about to write up a module that exports lots of functions. It's for testing purposes to create interesting, semi-random, complicated objects for testing which I refer as "The Sims" -- in the "let's create people and then throw them into a torturous environment and see how they react" sense.

Anyhow, it's going to export a huge pile of sim_* functions (whether or not that's a good idea is immaterial). I don't want to have to remember to update an @EXPORT variable dissociated from declaring the function. I'm going to forget. The obvious answer would seem to be to make exporting an attribute of the subroutine. And that's just what Exporter::Simple does. Unfortunately it doesn't really work and there doesn't appear to be an easy fix.

So... * Is there an existing module that works and does what I want? * If not, can Exporter::Simple be fixed? * If not, could you write an Export-by-attribute system that does work? * If not, could you write something that allows the export information to be conveniently placed near the subroutine?


Answers...

Alias on 2008-01-25T01:59:26

* Is there an existing module that works and does what I want?

Probably not, exporting is such a fundamental task that Export modules tend to be relatively back-compatible, and as such don't use attributes.

* If not, can Exporter::Simple be fixed?

If it involves changing it's Perl minimum version, no.

* If not, could you write an Export-by-attribute system that does work?

Quite probably.

I mean, it's just a case of a "use Attribute::Export;" setting the module to @ISA Exporter, creating the @EXPORT variable, and then having the attibute handler append the name to @EXPORT/@EXPORT_OK.

* If not, could you write something that allows the export information to be conveniently placed near the subroutine?

You mean like...

push @EXPORT, qw{function};
sub function {

}

If you aren't using attributes, you might as well just do that.

But I'd go down the attribute route.

Just beware you lose 5.6 support that way, so make sure that doesn't matter.

Re:Answers...

rjbs on 2008-01-25T15:42:45

I think Adam's solution (push onto @EXPORT) is pretty nice and simple, as far as things that deal with attributes can be. It's exactly what I would've suggested, although I probably would've built atop Sub::Exporter. ;)

I'll be interested to hear/see what you end up doing.

A quick and dirty suggestion

grantm on 2008-01-25T06:42:21

If in fact your module simply needs to "export a huge pile of sim_* functions", then one solution would be to write your own import() sub that scans your package's symbol table hash and exports every sub with a name that matches /^sim_/.

Perl6::Export

hanekomu on 2008-01-25T09:04:52

I'm the author of Exporter::Simple. It has some other bugs as well, and there's, like you said, no simple fix. Maybe Damian's Perl6::Export does what you want? The only thing - as far as I can remember - that Exporter::Simple does additionally is to be able to export variables, which Damian says is evil anyway. If Perl6::Export works better, then I was planning to deprecate Exporter::Simple.

Sub::Exporter + Class::MOP for the win!

Stevan on 2008-01-25T16:23:15

Well to start with, please please please make sure it's based on Sub::Exporter and not Exporter.pm. And as for basing it on attributes, I don't think that is a plus. Attributes are poorly implemented with a poor API to use them. They give you just enough power to think you can do something cool with them, and then fall short (IMO at least).

Okay, that said, here is a modified version of a proof of concept module I did recently that mixed Moose roles and Sub::Exporter. It should do the trick for exporting all ^sim_* functions without having to repeat yourself in @EXPORT or in attributes.

package Export::Only::What::I::Want;

use strict;
use warnings;

use Class::MOP;
use Sub::Exporter;

sub import {
    shift;
    my ($regexp) = @_;

    my $pkg = caller();

    my $pkg_meta = Class::MOP::Class->initialize($pkg);

    $pkg_meta->alias_method(import => Sub::Exporter::build_exporter(
            {
                exports => {
                    map {
                        $_ => $pkg_meta->get_method($_)->body
                    } grep {
                        /$regexp/
                    } $pkg_meta->get_method_list
                },
                groups  => { default => [':all'] }
            }
        )
    );
}
You would then use it in your module like so ...

package Foo;

use Export::Only::What::I::Want qr/^foo_/;

sub foo_bar { 'Foo::foo_bar' }
sub bar_foo { 'Foo::bar_foo' }
And whalla...

package Bar;
use Foo;
# Bar::foo_bar exists, but no Bar::bar_foo ..

- Stevan

yes

Eric Wilhelm on 2008-01-26T07:59:51

use export bar => sub {...};

What I haven't figured out yet is how to get an automatic unimport() call without userland $^H.

I'm convinced that it shouldn't involve Attribute::Handlers or anything else messing with UNIVERSAL::* (or @UNIVERSAL::ISA, eek!) and/or inheritance (certainly not inheritance of Exporter.pm.)

It could use attributes, probably by injecting compile-time MODIFY_type_ATTRIBUTES methods into your package, but it looks like something like 'no Exporter::NoReally' would be required to finish things (because the sub name doesn't get passed and I don't want to have an INIT block.)