UNIVERSAL::new for command line MU

Years ago, chromatic released Acme::UNIVERSAL::new. It was a joke. This is not.

Using object-oriented modules from the command line can be a PITA. I have to type the module name twice: once to load it and once to construct objects with it. In a program, that's not huge overhead, but in a one-liner, it annoys me every time.

So I wrote UNIVERSAL::new and a handy alias U. It lets you call "new" on a module name and have it loaded on the fly. Like this:

$ perl -MU -we 'HTTP::Tiny->new->mirror("http://www.perl.org/", "perl.html")'

If the module can't be loaded, or if the module doesn't have a "new" method, things just die in the usual way.

Of course, messing with UNIVERSAL is naughty, so save this for the one-liners, OK?

This entry was posted in hacks, perl programming and tagged , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

21 Comments

  1. Posted April 6, 2013 at 1:13 am | Permalink

    I believe L already does this.
    You may also be interested in a module I made, App::p, which uses L.
    With App::p, you could do:


    p 'HTTP::Tiny->new->mirror("http://www.perl.org/", "perl.html")'

    Or even:


    p 'mirror "http://www.perl.org/", "perl.html"';

    Since App::p provides support for a lot of utility functions out of the box.

    -Naveed

    • Posted April 6, 2013 at 7:13 am | Permalink

      On important difference is that L uses UNIVERSAL::AUTOLOAD. I don't like mucking with UNIVERSAL and wanted something less magical. App::p looks neat, though.

      • Steven Haryanto
        Posted April 9, 2013 at 12:01 pm | Permalink

        It'd be nice if L and other prior arts are mentioned in U's See Also.

        • Posted April 9, 2013 at 12:10 pm | Permalink

          I linked to Neil B's comparison -- which I scanned for something I could use before writing this. Apparently "L" slipped through his otherwise quite comprehensive dragnet.

          • Neil Bowers
            Posted April 10, 2013 at 10:59 am | Permalink

            Putting aside that I think PAUSE shouldn't allow single-letter-named modules, I've added UNIVERSAL::new and L to the list for the next update to the review :-)

            • Posted April 10, 2013 at 11:03 am | Permalink

              Thank you! Your module writeups are a great resource for people.

  2. Konstantin
    Posted April 6, 2013 at 3:09 am | Permalink

    Bravo! Very elegant.

  3. VZ
    Posted April 6, 2013 at 6:29 am | Permalink

    It is indeed elegant but unfortunately quite slow. I was regretting the need to write a long module name just yesterday when I was doing some simple searches in the mailbox using perl -MMail::MboxParser -E '... Mail::MboxParser->new("mbox") ... . It definitely looks much better with just -MU in the start but the original one liner was instantaneous (just measured it now and it was running in ~0.05s) while the new one is not -- it adds about 0.8s, which is noticeable amount of time, to the running time.

    So this is still very nice for longer running one-liners but if you want to not only type it fast but also run it fast, it's better to stick to writing the module name twice.

    • Posted April 6, 2013 at 7:15 am | Permalink

      Interesting. I don't terribly care about 0.8 seconds, but maybe I'll profile it and see if I can slim it down.

    • Posted April 6, 2013 at 9:11 pm | Permalink

      What platform are you on? And how are you testing the times. I don't see any noticeable runtime difference in my one off tests. (Mac OSX with a flash drive; testing with 'time').

      • VZ
        Posted April 7, 2013 at 4:23 am | Permalink

        Thanks for looking into this!

        I've tested it with just "time" under Debian Linux. The machine is relatively new machine (i7 2600 with lots of RAM) but doesn't use SSD, so the disk access latency could be the issue.

        Another possible explanation could be the difference in the Perl versions used as you're probably using something much more recent than 5.10.1 that Debian Squeeze has. Do you know if later versions improved anything that could affect this? Looking at the (few lines of) code in the module I don't see anything immediately obvious but I'm hardly an expert in Perl internals...

        And FWIW, of course I don't normally care about 0.8s difference neither. But the difference between "instantaneous" and "taking some time" is still noticeable and a bit annoying.

        • Posted April 7, 2013 at 9:32 pm | Permalink

          I'm loading modules with Module::Runtime, which works around some bugs in older Perls, including 5.10.1. That could be an issue. I'll do some more testing and see. For this, I wouldn't cringe too much to do a string eval require if that shaves a big chunk of time off.

  4. Posted April 9, 2013 at 6:50 am | Permalink

    I wish it did VERSION as well as new. I seem to do this

    $ perl -MHTTP::Tiny -E 'say HTTP::Tiny->VERSION'
    0.017

    a lot, which also suffers from the name the module twice problem. It would be nice to say

    $ perl -MU -E 'say HTTP::Tiny->VERSION'

    instead. I'm not going to remember to say

    $ perl -MU -E 'say HTTP::Tiny->new->VERSION'
    0.017

    Note that L has the same issue

    $ perl -ML -E 'say HTTP::Tiny->VERSION'

    $ perl -ML -E 'say HTTP::Tiny->new->VERSION'
    0.017

    • Posted April 9, 2013 at 9:48 am | Permalink

      I don't think that's possible because UNIVERSAL::VERSION already exists and overriding that is... uh... even more crazy than creating UNIVERSAL::new.

      I solve that particular problem with a bash function: pmver () { perl -M$1 -le 'print shift->VERSION' $1 }

    • Posted April 9, 2013 at 7:31 pm | Permalink

      There’s already V for that:

      perl -MV=HTTP::Tiny

  5. Posted April 18, 2013 at 7:42 am | Permalink

    Some problems with this:

    $ perl -MU -E 'Mojo::UserAgent->new'
    Can't locate object method "detect" via package "Mojo::Reactor::Poll" at /home/jest/perl5/perlbrew/perls/5.16_threads/lib/site_perl/5.16.3/Mojo/IOLoop.pm line 23.
    Compilation failed in require at /home/jest/perl5/perlbrew/perls/5.16_threads/lib/site_perl/5.16.3/Mojo/UserAgent.pm line 8.
    BEGIN failed--compilation aborted at /home/jest/perl5/perlbrew/perls/5.16_threads/lib/site_perl/5.16.3/Mojo/UserAgent.pm line 8.
    Compilation failed in require at /home/jest/perl5/perlbrew/perls/5.16_threads/lib/site_perl/5.16.1/Module/Runtime.pm line 317.
    at -e line 1.

    Don't know who to blame :)

    • Posted April 18, 2013 at 6:17 pm | Permalink

      Mojo::Base apparently has $flag->can('new') as a way to check if it needs to load a module (instead of checking %INC) and UNIVERSAL::new makes that true for everything.

© 2009-2014 David Golden All Rights Reserved