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

How do you do multi-version modules?

by perl-diddler (Chaplain)
on Oct 13, 2012 at 22:56 UTC ( [id://998882]=perlquestion: print w/replies, xml ) Need Help??

perl-diddler has asked for the wisdom of the Perl Monks concerning the following question:

I have a module I created .. and realized I wanted to change something in the interface. So... I've been 'versioning' each module change, and would bump the version in the new module.. BUT, how could my module detect -- when a user says: "use module 1.0.0", and I'm working in module "2.0.0"? My 2.0.0 module would never get called as far as I can tell...no?

What I would want to do is have my 2.0.0 module detect if it was called by something prior to 2.0, and apply the old interface semantics for compatibility.

The only way I can think of is to NOT version my code, but expect that any "using modules" will use "some version" (which? .. How would they know if I don't put a version in my code?) but then if I was called as a versionless module, I think I might receive the version number as a parameter, and could then implement the behavior I want -- but it seems that to do that I have to NOT use a version number -- which would sorta make it hard for any new code to know what version to specify to get behavior "X"...

Is it possible to do this? Or prohibitively difficult?

Thanks for ideas, suggestions, clue-sticks!...

Linda

Replies are listed 'Best First'.
Re: How do you do multi-version modules?
by Corion (Patriarch) on Oct 13, 2012 at 23:11 UTC
    when a user says: "use module 1.0.0", and I'm working in module "2.0.0"? My 2.0.0 module would never get called as far as I can tell...no?

    What have you tried so far? What evidence do you have that supports your assumption?

    The easy way to do that is to separate "module version" and "API version", and have the use line pass the API version as parameter. I recommend making the main module just a stub module and having "::API*" modules as submodules that get loaded resp. instantiated depending on what the user wants.

    For example, a module could implement API1 and API2 in the following structure:

    package My::Module; use strict; sub import { my ($class, %args) = @_; ... # Dynamically load either My::Module::API1 or My::Module::API2 };
    package My::Module::API1; # The old API
    package My::Module::API2; # The new API

    User code would then use it in the following way:

    use My::Module api => 'API1'; ...

    Look at require and/or use to see how to do dynamic module loading.

      That would defeat the purpose of having 1 module handle both.

      I don't want to use multiple files for the same module -- duplicating code is a bad practice.

      One of the reasons I tend to put many modules in 1 file during development, is to find places to look for common code to apply reductions to. Only after some package has sat in my file doing nothing useful (other than providing it's function) other than than taking up useful space in my file.. I consider splitting it off... I.e. it's so far out of my head space, because I'm just "using the code", that it's not changing, and it's presence in the file is now a bother ... time to split it off and take it as a library component...OR...that might be too much of a bother...until I'm working on another program that wants to use the module...that's when I really work on splitting things...

      But I would think I had too many routines shared between the versions. I was only thinking of forcing a different parameter order on 1 routine -- would you really think it a good idea to duplicate hundreds of lines just to change a few?

        Maybe you want to reuse parts of ::API1 in ::API2, then? It's only you who claims that you have to duplicate code between the modules. Maybe having a preconfigured API "object" which contains the default parameters would be an option too, if the "API" only consists of default parameters.

        This has nothing to do with putting many modules in one file. You've already been advised against that, but this discussion is far besides the point.

        You don't have to put different packages in different files, only when you need to make them available to use from other files. SOAP::Lite comes to mind as a module where it exhibits much different behavior depending on what's needed (eg, do we use 'xsi:null' or 'xsi:nil' for this version of XML Schema?

        SOAP::Lite might be a bit of an extreme example, as they pack a *lot* into one file. (I count 28 package declarations currently, and it looks like more might be dynamically generated; I remember bugging BareBones Software for *years* about marking packages in automatically generated list of functions so that I could use it to find my way around the file).

Re: How do you do multi-version modules?
by kcott (Archbishop) on Oct 13, 2012 at 23:30 UTC

    G'day Linda,

    "... when a user says: "use module 1.0.0", and I'm working in module "2.0.0"? My 2.0.0 module would never get called as far as I can tell...no?"

    use Module VERSION specifies a minimum version. If the user has v2.0.0 installed and their code has use Module 1.0.0;, this will work fine.

    "What I would want to do is have my 2.0.0 module detect if it was called by something prior to 2.0, and apply the old interface semantics for compatibility."

    You can add a VERSION method to your class to achieve this.

    This is described in use. This doco may be a little confusing: you want the use Module VERSION info (near the middle of the page); not the use VERSION info (near the top of the page).

    -- Ken

      Thanks for the tip...I think in all the other stuff about use, I missed that section... Will have to go study it again...(so many places are like that -- especially when functions get updated...)... Yup, that sounds about perfect... didn't realize version was an overridable universal method...
Re: How do you do multi-version modules?
by ikegami (Patriarch) on Oct 13, 2012 at 23:53 UTC

    Polymorphic API:

    If this is an OO-based module, you could pass a protocol version to the constructor. (Kinda like many file formats include a version.) If it's not, you could write your own import sub and use use module "1.0.0"; instead of use module 1.0.0;. This would be a bit error prone.

    Parallel APIs:

    The other approach is add to the interface instead of replacing it. You can use two different module names: "module" for 1.x.x and "module2" for 2.x.x, or you can add methods instead of replacing old ones (do_something2 is the new interface for do_someting).

      If this is an OO-based module, you could pass a protocol version to the constructor. (Kinda like many file formats include a version.) If it's not, you could write your own import sub and use use module "1.0.0"; instead of use module 1.0.0;. This would be a bit error prone.

      And if you go this route, the first version that handles protocol/API/etc. versions should assume that "no API version given" is a valid input that means "the oldest version that did not know how to handle API versions", for the sake of backwards compatibility.

      I wish I'd thought of that in designing the 1st... I thought about passing the version... but the earlier code would still have to be rewritten .... THOUGH, it is SIMILAR to what I was thinking about... and that is adding a new param, that the new stuff looks for but if not there, uses old behavior...(I think someone else suggested similar)...

      Still... gonna have to look at use more ... sounds like there may be some version stuff I missed... Though I'd think I'd go the opposite on the defaults w/o the version number. I don't want to have to support old stuff forever. So unversioned calls would get the newest behavior. It's assumed that most people want the newest and latest and greatest unless they really ask for the the outdated stuff, I don't see why software should assume the opposite is true. Imaging if people ran programs that didn't ask for a version on Windows 7 and got Dos 1.0 behavior... :-)....pretty screwed up eh?

Re: How do you do multi-version modules?
by tobyink (Canon) on Oct 14, 2012 at 08:26 UTC

    In general, it is vastly simpler to have two separate modules. That said, here's an example including test cases for a module which exports three functions (alphabet, colours and numbers) where two of the functions have different behaviours in API 1 and API 2.

    The key trick here is using Sub::Exporter to install different coderefs into the caller's package depending on what API version they have requested. Some of the examples even show how a caller can request parts of API 1 and parts of API 2.

    I've used an Exporter-type module because that's the most challenging to implement. If you're writing object-oriented code instead, then things are probably a bit easier. You can usually just create two subclasses of a base class.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
Re: How do you do multi-version modules?
by CountZero (Bishop) on Oct 14, 2012 at 15:45 UTC
    Not having a version number is a bad idea. It will lead to all kinds of misunderstandings. How can anyone tell you what "version" they have installed if there is no version number?

    If you ask me, I'd have no less than 3 modules: one with API1, one with API2 and one with all common code to API1 and API2.

    Another solution is, depending on your code, to have the API modules contain only your public interface. Then all "working" code is in the third module. That will make it easier to maintain the whole by separating the concerns "interface" and "working code".

    Or the API1 module could contain some routines that "translate" API1 calls into their API2 version and then handover execution to API2 which calls upon its own routines. You could do without a third module in this case.

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

    My blog: Imperial Deltronics
Re: How do you do multi-version modules?
by davies (Prior) on Oct 14, 2012 at 22:03 UTC

    Not an answer to your question, rather something you may want to include. I was told about semantic versioning (http://semver.org/) at a London PM meeting, and have passed it on to a few people, all of whom say it's a fine idea. YMMV.

    Regards,

    John Davies

Re: How do you do multi-version modules?
by DrHyde (Prior) on Oct 15, 2012 at 10:17 UTC

    > when a user says: "use module 1.0.0", and I'm working in
    > module "2.0.0"? My 2.0.0 module would never get called as
    > far as I can tell...no?

    It would be very easy for you to test this and see what happens. It would also be easy for you to read the documentation for use, which tells you what will happen.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (4)
As of 2024-04-24 06:43 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found