develooper Front page | perl.qa | Postings from September 2016

Need feedback on an Importer interface design

From:
Chad Granum
Date:
September 12, 2016 21:23
Subject:
Need feedback on an Importer interface design
Message ID:
CAJFr3kvb+tAqWdpX7bu+G3bVE-i7oxD8oau94i_D6Lc-fuv3nw@mail.gmail.com
This question pertains to a new feature I have been working on for
Importer: https://metacpan.org/release/EXODIST/Importer-0.021-TRIAL

The trial release above has some known bugs, I have fixed a few, but have
not put them on github/cpan yet, be aware of that before trying it out.


I have been working on a feature called "pins". Pins are like an enhanced
form of tag. Basically pins are alternative sets of exports. The reason for
doing this is to provide a way to make backwords incompatible changes to
exports without breaking anything.

Silly Example:

package Foo;

our @EXPORT = qw/foo/;

sub foo {
    my ($thing1, $thing2) = @_;
    ...
}


Now, say it is determined that foo() taking 2 arguments is really really
bad, so we want to make foo() only take 1 argument, and die if it gets a
second. This will break any consumers that use foo() with 2 arguments.

One option would be to keep foo() how it is, and create a new sub like
foo2(). And maybe give foo() deprecation warnings. But sometimes making a
new sub is not viable, specially if you force everyone to always use the
old name with '2' appended.

Another option would be to make foo() accept both forms, and warn that the
2 arg form will go away in a future release, though in practice it never
will, and if it did people who take too long to upgrade will miss the
deprecation window and it will just break when they upgrade.

Another option is pins. With pins you specify a default export sets, and
pinned export sets:

package Foo;

our %EXPORT_ANON = (
    foo => \&legacy_foo,
);
our %EXPORT_PINS = (
    root_name => 'v1', # Make default/no-pin be named 'v1'
    'v2' => {
        inherit => 'v1',
        export_anon => { foo => \&new_foo }
    }
);

sub legacy_foo { ... }
sub new_foo { ... }

Now what this does for consumers:

use Foo qw/foo/; # Get the legacy (don't break existing consumers
use Foo qw/+v2 foo/; # Get 'foo' from the v2 pin

Basically if you do not specify a pin it will do what it has always done,
nothing breaks. If you prefix exports with '+v2' the symbols listed next
come from the 'v2' pin.

Other "features" of pins:
 * Each pin has a complete export menu, default exports, optional exports,
anon exports, etc.
 * A tag is automatically added for each pin, so you can use ':v2' to get
all the default exports from the 'v2' pin.

There are a couple options for consumers, for instance these are identical:

use Importer 'Foo' => ('+v1' => [qw/a b/], '+v2' => [qw/c d/]);
use Importer 'Foo' => qw/+v1 ... +v2 .../;

The main difference is the first uses arrayrefs to make it more clear that
some symbols are from some pins, other symbols are from other pins.

Anyway, I have this mostly implemented, and I do have a need for it. What I
am looking for is feedback about what sucks from this, what is good from
this, etc. I would rather have the feedback before a release as opposed to
after when it might be harder to change things.

Thanks!



nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About