Removing Perl Boilerplate with Import::Into

| No Comments | 1 TrackBack

I recently started a new Perl project and wanted to take advantage of some cool new Perl 5.20 features. In particular, I wanted to use the experimental subroutine signatures feature and postfix dereferencing. That requires the following incantation:

use feature 'signatures';
use feature 'postderef';
no warnings 'experimental::signatures';
no warnings 'experimental::postderef';

That can be abbreviated a bit by passing a list to feature and just turning off the entire experimental class of warnings:

use feature 'signatures', 'postderef';
no warnings 'experimental';

But it's still a couple lines of boilerplate that need to be pasted atop every file in my project. I hate pasting things.

I had previously experimented with various ways to automate the importing of a large list of modules and pragmas to avoid pasting boilerplate code. Some of these adventures were more successful than others, but I've recently settled on using Import::Into, which easily solves the problems I ran into when trying to roll my own.

You may already know that whenever you use

use Foo LIST;

It's equivalent to:

BEGIN { 
    require Foo.pm;
    Foo->import( LIST );
};

It's the import method that does the magic, from turning on lexically-scoped compiler options to copying symbols into your namespace.

A problem is that the import method can be implemented in any fashion the module author desires. Many simply inherit the default import from Exporter. Others use Sub::Exporter or a totally custom import method. That's fine for ordinary client code, but can be bothersome if you want to use a class to import symbols (and behavior) into a different class. There is no consistent API for this, and some imports don't support it at all.

The solution to all this pain is not trivial, but it's explained well in the Import::Into documentation. Go ahead and read it, if you're interested.

With Import::Into in my toolbox, doing what I want became pretty easy. I started with a module called MyApp::Setup, which begins thusly:

package MyApp::Setup {
    use strict;
    use warnings;
    use autodie;
    use feature ':5.20';
    use Carp 'croak';

    use Import::Into;

Then I made sure I loaded the rest of the modules that I wanted to import into my calling classes. I passed them an empty list on the use line so they would not affect my Setup module directly.

    use JSON::XS         ();
    use File::Slurp      ();
    use Scalar::Util     ();
    use List::MoreUtils  ();
    use Moose            ();
    use Moose::Role      ();
    use boolean          ();

Now, I can specify my own import method which will use Import::Into under the hood to work its magic. The Import::Into package actually defines a subroutine called import::into (that is, a sub into in package import). You can use a fully-qualified subroutine name as a method on any invocant, including class names, which makes things easy.

    sub import {
        my ( $class, $type ) = @_;
        croak 'must specifiy class or role for MyApp::Setup'
            unless $type;

        my $target = caller;

        # we have to do Moose first since we're going to override
        # its pragma exports with our own stuff
        if ( $type eq 'class' ) {
            Moose->import::into( $target );
        } elsif ( $type eq 'role' ) {
            Moose::Role->import::into( $target );
        }

        # import pragmata into calling class
        strict  ->import::into( $target );
        warnings->import::into( $target );
        autodie ->import::into( $target );
        feature ->import::into( $target, ':5.20' );
        feature ->import::into( $target, 'signatures' );
        feature ->import::into( $target, 'postderef' );
        boolean ->import::into( $target );

        warnings->unimport::out_of( $target, 'experimental' );

        # import useful modules that we'll want to use a lot
        JSON::XS       ->import::into( $target, 'decode_json', 'encode_json' );
        File::Slurp    ->import::into( $target, 'read_file' );
        Scalar::Util   ->import::into( $target, 'reftype', 'blessed' );
        List::MoreUtils->import::into( $target, 'any', 'all', 'apply', 'uniq' );
    }

One wrinkle that took me quite a while to figure out is that order is important. Since Moose and Moose::Role also import strict and warnings into your class, they will override any unimports of experimental warnings that you want. Therefore, we handle the Moose stuff first.

Using the setup module in the rest of my project is extremely easy:

package MyApp::Something { 
    use MyApp::Setup 'class';

    # I now have Moose, strict, warnings, experimental features, 
    # autodie, and useful functions. 
}

1;

I'm pretty happy with this solution, but would love to hear if you have another way.

1 TrackBack

TrackBack URL: http://friedo.com/cgi-bin/mt/mt-tb.cgi/23

Perl Boilerplate from Negativespace.net on June 8, 2014 3:56 PM

Mike Friedman posted about cleaning up Perl boilerplate and switching to Import::Into. I like his ideas, and in particular I will be using parameters to my Setup packages from now on. This is my import subroutine. [perl] sub import { my $target = calle... Read More

Leave a comment

About this Entry

This page contains a single entry by Mike Friedman published on June 4, 2014 1:43 PM.

Arrays vs. Lists in Perl: What's the Difference? was the previous entry in this blog.

Automating Stratopan Releases with Perl's Dist::Zilla is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Categories

Pages

Powered by Movable Type 5.14-en