Comments are solicited at this point in the design. It may be a couple weeks before I proceed with an implementation.

In discussing versioning exports, I realized that the fundimental feature lacking in Exporter is the ability to have the internal and exported name differ. That is, an alias ability.

Versioning then is an application of using different aliases for different versions.

So I'm tennitivly calling this module Exporter::VA, for Version and Alias abilities.

Here is the user interface; that is, how a module author takes advantage of these features.

There are several kinds of information: as with the traditional system, there are symbols that are allowed to export, a subset of those that are default, and tags for named sets of symbols.

I want to have aliases that differ by version, as well as the (hopefully common) case of changing which symbols are exported by default based on version.

To that end, I've combined all the information into one hash. Here is an example:

use Exporter::VA; %EXPORT= ( foo => [ v1.0, 'foo_old', v2.0, 'foo_new' ], bar => 'bar_internal', baz => 'baz', fobble => \&figure_it_out, # parameter list is (caller, version, sy +mbol, param-list tail) $x => '$x', :tagname => [ 'foo', 'bar' ], :DEFAULT => [ v1.0, ':_old_imports', v2.3, ':_new_imports' ], :_old_imports => [ 'foo', 'bar', 'baz', 'fobble' ], :_new_imports => [ 'fobble' ] ); Exporter::VA::plain qw/ baz $x /;
The %EXPORT package variable is a set of hash pairs. If the key begins with a : then it is a "tag", described below. If it is an identifier name or begins with a sigel, it is a symbol name.

Symbol Entries

A symbol name (in the key or value) may omit the & for functions.

The value may be the name of the internal symbol; a code reference; or an array ref.

If simply a string, it is the name by which the symbol is defined within the module. foo => 'foo_x' means to take the package's &foo_x and make it known as &foo in the caller's namespace. If the value name is equal to the key name, then no renaming is done (see Exporter::VA::plain below).

If the value is a list, the list alternates v-strings and symbol names. The symbol name will be taken when it appears between two v-strings such that the desired version is >= to the left and < the right.

If the value is a code ref, then it is called to figure it out at run time. It must return a reference to the proper type of thing, and that will be entered into the caller's namespace. It is called with the caller's package name, desired version v-string, the symbol name, and a reference to the rest of the import parameter list. The last allows the callback to pull more arguments (and delete them), or otherwise modify the remainder of the list.

Tag Enteries

A key that begins with a : character is a tag, and they work like the traditional feature. If you import a tag name, it is expanded into the list of names automatically.

The value is a list of names (symbol names and other tag names are allowed) or a list of alternating v-strings and tag names.

If a list of names, the list replaces the tag in the import parameter list and processing continues with the expanded content so nested tag lists are handled.

If a list of alternating v-strings and tag names, the logic is like that for symbol names. The tag is replaced by the other tag based on the desired version, and processing continues with that tag.

The :DEFAULT tag is used if an empty argument list is imported. It is just like any other tag, just taken as a default.

they work together

If a package imports the default in the above example, it will note that foo was imported in versions before v2.3 but dropped as a default in that version. But, in version 2.0 the implementation changed, so it will pull in foo_old if the requested version is less than 2, or foo_new if between 2.0 and 2.3, or not import it at all if 2.3 or later.

plain

Not every symbol has a version decision or alias. Degenerate cases such as  baz => 'baz', $x => '$x' are a bore.

So, the function call Exporter::VA::plain qw/ baz $x /; will populate these simple entries. Each argument to plain is added as an entry to %EXPORT whose value is the same as the tag.

Aliases and Non-imported calls

An alias only works if imported! If Module::foo was called directly, it would indeed try and call a function named foo, not foo_old or foo_new.

This thing only imports. This issue is different, but this thing will help a little.

If you indeed write a sub named foo, it could check the caller and decide at run time what to do based on which version the caller asked for.

To this end, the package variable %EXPORT_CALLERS associates importers with v-strings as supplied in their use statement.

The function Exporter::VA::resolve (symbol, optional-vstring) may also be used to traverse the %EXPORT data for you. If the v-string is omitted, it is looked up based on the caller's package.

default version

The package global $EXPORT_DEFAULT_VERSION may be defined. If so, it is taken as the version if no v-string is given in the use statement. Otherwise, a v-string is required in the use statement.

Replies are listed 'Best First'.
Re: Module Design strawman - Exporter::VA
by theorbtwo (Prior) on Oct 04, 2002 at 20:31 UTC

    A couple of API changes I think would make things cleaner:

    • You shouldn't have to have %EXPORTER as a global (use vars, our) variable. Instead (or additionaly), you should be able to pass a hashref (or just a hash?).
    • You should be able to specifiy hard references instead of symbol names. (Together, these two would let you avoid having any globals if you want -- and I want.)
    • Instead of having that single function call in what is otherwise a purely declarative API, have a key value of '' mean "symbol name same as export name". (Perhaps a value of undef should mean that the import is silently ignored -- I'm not sure if such a thing should be encouraged, though.)
    • Perhaps you could autogenerate the direct-callable functions (IE create a sub foo{} that calls the correct foo_old or foo_new, if sub foo doesn't exist).
    • Document that pragmata imports traditionaly begin with a - and can be implemented using \&figure_it_out.
    • Resolve the conflict I just created with exporting arrays and code via the reference syntax and the tag and \&figure_it_out syntax. (I really think that you should be able to export via hardrefs rather then symbolic refs, though.)
    • $EXPORT_DEFAULT_VERSION should be doable in the hash too, rather then as a seperate global. (Perhaps _Exporter_VA_defaultVersion.) Also, if it's not given, it should default to $VERSION, not have no default.


    Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

      • You should be able to specifiy hard references instead of symbol names. (Together, these two would let you avoid having any globals if you want -- and I want.)
      • Resolve the conflict I just created with exporting arrays and code via the reference syntax and the tag and \&figure_it_out syntax. (I really think that you should be able to export via hardrefs rather then symbolic refs, though.)
      I didn't do hard refs because of this conflict. I suppose you could say foo => sub { \&foo_hard } with the existing design. Any other design (additional decorations of some kind?) would need to be at least this simple.

      • You shouldn't have to have %EXPORTER as a global (use vars, our) variable. Instead (or additionaly), you should be able to pass a hashref (or just a hash?).
      You mean right in the use line, like
      use Exporter::VA (foo => 'foo_internal');
      • Perhaps you could autogenerate the direct-callable functions (IE create a sub foo{} that calls the correct foo_old or foo_new, if sub foo doesn't exist).
      Wonderful. An inherited AUTOLOAD would do the trick.

      • Instead of having that single function call in what is otherwise a purely declarative API, have a key value of '' mean "symbol name same as export name".
      I suppose that would be OK. Also, a special hash entry could list all the non-alias simple exports, instead of using a function call.

      • Document that pragmata imports traditionaly begin with a - and can be implemented using \&figure_it_out.
      Sure. Is there any prior art on pragmatic imports other than version numbers?

      • $EXPORT_DEFAULT_VERSION should be doable in the hash too, rather then as a seperate global. (Perhaps _Exporter_VA_defaultVersion.) Also, if it's not given, it should default to $VERSION, not have no default.
      If not given, default to the same version number as the Exporter::VA's version? I don't get it.

      Everything in the hash: Sure could. But I think it should be clearer than that.

      Thanks for your thoughts. That was all very useful feedback. Stay tuned for the next iteration.

      —John

      re: Resolve the conflict I just created with exporting arrays and code via the reference syntax and the tag and \&figure_it_out syntax. (I really think that you should be able to export via hardrefs rather then symbolic refs, though.)

      See callback or call now?.

Re: Module Design strawman - Exporter::VA
by Anonymous Monk on Oct 05, 2002 at 01:10 UTC
    Don't repeat some of Exporter's mistakes. Default exports and export tags are not generally a good idea. Users often do not want your names for your functions and should be able to rename them in the import. There is a lot of parsing work figuring out how to export variables. Don't do that multiple times. Don't force people to inherit.

    Just do variables and functions in the first pass. Figure out a syntax that allows users to rename variables. Have the module author calls a function and it builds an import for you.

    Worry about enhancements later.

      There is a lot of parsing work figuring out how to export variables. Don't do that multiple times.

      What do you mean?

      Users often do not want your names for your functions and should be able to rename them in the import.

      How often? I figured if you don't want that name, don't import it! Renaming on import would create apparently different interfaces to the same module, and make it harder to understand. I've never seen this discussed as a problem. Can you go into it more?

      Certainly, I could do it with a array ref in the import list:  use MyModule v2.03 ('foo', [bar => 'renamed_bar'], 'baz');

      Don't force people to inherit.

      I suppose importing import from Exporter::VA would accomplish the same thing as inheriting from it. What is the disadvantage of inheriting from it?

      —John