Sign In/My Account | View Cart  
advertisement


Listen Print

For Perl Programmers : only
by Brian Ingerson | Pages: 1, 2

For Argument's Sake

There's not much to say about passing arguments. Just pass them in the same way you would on a normal use statement. This should even work for modules like Inline.pm where the arguments are not import lists:


    use only Inline => 0.44, 'Java';

There is one exception. In Perl, when you say something like:


    use Dog::Walk ();

That's a cue to not call the module's import method at all. In other words, it ensures that no functions will be exported into your namespace. But if you say:


    use only Dog::Walk => 1.00, ();

then you will not get the same effect. Unfortunately, there is no way for only.pm to detect that you called it that way. As a workaround, only lets you say:


    use only Dog::Walk => 1.00, [];

This has a similar visual appearance and is meant as a mnemonic. (Hopefully, there aren't a whole lot of modules in the world where it is important to pass in a single empty array ref :)

The Only Options

The only (no pun intended) option currently implemented for only is versionlib. This option allows you to override the system versionlib stored in only::config.


    use only { versionlib => '/home/ingy/modules' },
        Corn::Stalk => 0.99, [];

Friends and Family

One important duty of only is to ensure that when you load a specific version of some module, all of that module's related modules are also loaded from the same version level. This is tricky, because in Perl, you never know when a module is going to be loaded. It could be loaded by your original module or not. It might happen at compile time ( use ) or run time ( require ). It could be loaded hours later in a long-running process (or a very, very, very slow computer :) There might also be autoloaded functions involved.

Most importantly, some of the sub-modules might be loaded using use only, while others are loaded with standard use and require statements. To make all this happen the way you'd expect it to, only plays some tricks with @INC. More on that shortly, my preciouses.

only knows which modules are related because it saves the information as metadata for every module it installs. For example, if I install a module like so:


    $ cd YAML-0.35
    $ perl Makefile.PL
    ... lines deleted
    $ make test
    ... lines deleted
    $ perl -Monly=install
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML.pm
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML.pod
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML/Error.pm
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML/Family.pm
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML/Node.pm
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML/Transfer.pm
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML/Error.yaml
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML/Family.yaml
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML/Node.yaml
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML/Transfer.yaml
    Installing /usr/local/perl580/lib/version/5.8.0/0.35/YAML.yaml

then I get a YAML metadata file for each module. The metadata file YAML.yaml looks like this:


    # This meta file created by/for only.pm
    meta_version: 0.25
    install_version: 0.35
    distribution_name: YAML
    distribution_version: 0.35
    distribution_modules:
      - YAML.pm
      - YAML/Error.pm
      - YAML/Family.pm
      - YAML/Node.pm
      - YAML/Transfer.pm

This way, no matter which module is loaded first, only knows about every other module that was installed with that module.

Only on the Inside

The internals of only.pm are not incredibly complicated, but there is a little black magic going on. Most of it boils down to a relatively new and under-publicized feature of Perl5: putting objects onto the @INC array.

As you probably know, @INC is a special global array of file-system paths. When a program tries to load a Perl module with the use or require commands, it searches each of these paths in order until the module is found. The default paths in @INC are compiled into Perl. You can alter the array with the PERL5LIB environment variable, the lib.pm module, or even by simply changing it with regular Perl array commands. It's just an array, after all.

As of Perl 5.6.1, you can actually put Perl objects onto @INC and have use and require interact with them. When require encounters an object in @INC it attempts to call that object's INC method. The INC method can do anything it wants to load the module. It could actually go out on the internet and locate the module, download it, install it and load it!

The INC method should either return a filehandle or nothing. If a filehandle is returned, then require considers the operation a success. It reads the contents of that filehandle and eval-s the module into existence. If nothing is returned, then the operation is considered unsuccessful, and require continues its merry way down @INC.

The heart of the only module's magic lies in the fact that it puts an object onto @INC that is responsible for loading an appropriate version of your module. Not only that,it is also responsible for loading the matching version of any related modules that were installed at the same time as your module.

O-O-o - Object-Oriented Only

Since only is an object-oriented module on the inside, it is no surprise that it offers an OO API to the those of you on the outside. (I assume that you don't live inside a Perl module :)

Using the OO interface can give you more understanding and control of the version specific loading process, at the cost of a slightly more verbose syntax specification. As an example, if you would normally do this:


    use only Good::Stuff => '1.20-1.55';

then you can say the same thing by doing this:


    use only;
    my $only;
    BEGIN {
        $only = only->new;
        $only->module('Good::Stuff');
        $only->condition('1.20-1.55');
        $only->include;
    }
    use Good::Stuff;

Note that the use statement is absolutely normal. No only involved. But it still does what we want! That's because the preceding code sticks one of those magic only objects onto @INC.

The methods should be fairly self-explanatory. The key method call is only-include>. It tells the object to attach itself to the front of @INC.

One nice thing is that you can actually do stuff with the object later on in the program:


    use only;
    my $only;
    BEGIN {    # methods can be stacked
        $only = only->new->module('Good::Stuff')->condition('1.20-1.55')->include;
    }
    use Good::Stuff;
    ...
    print "Using Good::Stuff version: " . $only->distribution_version . "\n";
    ...
    $only->remove;    # Remove object from @INC;

Conclusion

I always believed that if my old Triumph motorcycle ever broke down out on the open road, I could somehow figure out a way to repair it by walking down the road for a half mile in either direction, and finding some odds and ends lying around that I could use for tools. That's because the roads are usually littered with all sorts of weird things, and I see everything as a tool to somehow suit my needs.

Perl is much like that roadside. There are all kinds of weird things lying around that can help it solve its own problems. Even when Perl is playing the part of a busted Triumph bike, its roadside qualities always seem to be able to kickstart it right back into action.

only.pm is a great example of this. Even though Perl was inadequate in regards to module versioning yesterday, today, it's packing a brand new chainsaw. Rev it up!

About the Author

Brian Ingerson has been programming for more than 20 years, and hacking Perl for five of those. He is dedicated to improving the overall quality of scripting languages including Perl, Python, PHP and Ruby. He currently hails from Portland, Ore. -- the location of this year's O'Reilly Open Source Convention. How convenient!