Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid

Comment on

( #3333=superdoc: print w/replies, xml ) Need Help??

Recently, I've been thinking about a really, really minor perl issue: what's the best way to format your script's main routine? I'd also always wondered how you were supposed to unit-test the main routine in your script. I recently came up with an idea (inspired by a brian_d_foy article) that answers both questions for me.

When I first started coding, I just put everything in my main routine at top level-- in global scope. My scripts looked something like this (translated from perl 4):

#!/usr/bin/env perl use strict; use warnings; # Script variables our $Foo = 'bar'; # Main routine my ($name, $greeted) = @ARGV; $name //= 'Horace!'; $greeted //= 'world'; say_hello($greeted); # Subroutines sub say_hello { my ($name) = @_; print "Hello $name\n"; }

The problem with that format is that any variable I have in the main routine, like $name above, is in scope for the whole file. If I'd misspelled $name in the argument list to say_hello(), it could have given rise to a hard-to-trace bug.

I've seen some people solve this problem by declaring a subroutine called main(), then calling it immediately afterward. This solves the immediate problem, but makes the code a little more confusing, at least to my eyes:

# Main routine sub main { my ($name, $greeted) = @ARGV; $name //= 'Horace!'; $greeted //= 'world'; say_hello($greeted); } main();

It works, of course, but something about it bothers me. It's easy to miss the call to main() when reading the code, and my eyes tend to skip over the subroutine when looking for the main routine. Worse, if the main() call gets deleted, the entire script will fail to run with no error or warning.

The style I adopted uses a named block to split the difference between subroutine and toplevel code, like so:

# Main routine MAIN: { my ($name, $greeted) = @ARGV; $name //= 'Horace!'; $greeted //= 'world'; say_hello($greeted); }

Here, I thought, was the One True Main Routine Style. My main routine is in a lexical block, but is automatically executed whenever I run the script. A friend of mine pointed out the issue with this code, though: I should call exit(0) afterward, to prevent any other code from being run:

# Main routine MAIN: { my ($name, $greeted) = @ARGV; $name //= 'Horace!'; $greeted //= 'world'; say_hello($greeted); exit(0); }

OK, that works, but the extra statement in every script got me thinking. Could I create some syntactic sugar that makes it obvious where the main routine is, but means that I don't have to remember to type 'exit' in every script?

The answer was inspired by a brian_d_foy article, "Five Ways To Improve Your Perl Programming", in which he describes modulinos, which are programs declared as modules. The key here is that he uses the 'sub main' method, but combines it with a function call that only happens when the file is run as a script. That lets you still use the package as a library, or write unit tests for it. Still, the code was a little more complicated than I wanted to write every day:

package My::Routine; # ... # Main routine sub main { my ($name, $greeted) = @ARGV; $name //= 'Horace!'; $greeted //= 'world'; say_hello($greeted); } # Run the main routine only when called as a script __PACKAGE__->main() unless caller;

Today, it occurred to me that I could write a module that provides some syntactic sugar for the perfect main routine. It would be simple and obvious, provide a lexical block, and exit afterward. In short, like this:

#!/usr/bin/perl use Devel::Main 'main'; # ... # Main routine main { my ($name, $greeted) = @ARGV; $greeted //= 'world'; say_hello($greeted); };

It might seem odd to make syntactic sugar that does so little, but this is the best solution I've come up with. The 'main' block will run if this file is called as a script. If it's brought into another script via require() or use(), the main routine won't run, but it will create a function called run_main() that will call the main routine with @ARGV set to its arguments. That way, I can write a test script like so:

#!/usr/bin/env perl require ''; print "Loaded script!\n"; run_main('Shakespeare!', 'perlmonks');

Which would print:

$ perl Loaded script! Hello perlmonks

So here's the code that provides the syntactic sugar. Honestly, I wouldn't be surprised if someone has already done this on CPAN, but a cursory search didn't show me anything.

use strict; use warnings; # Devel::Main by stephen package Devel::Main { # We use Sub::Exporter so you can import main with different names # with 'use Devel::Main 'main' => { -as => 'other' } use Sub::Exporter; Sub::Exporter::setup_exporter({ exports => [ qw/main/ ]}); # Later versions will let you customize this our $Main_Sub_Name = 'run_main'; sub main (&) { my ($main_sub) = @_; # If we're called from a script, run main and exit if ( !defined caller(1) ) { $main_sub->(); exit(0); } # Otherwise, create a sub that turns its arguments into @ARGV else { no strict 'refs'; my $package = caller; *{"${package}::$Main_Sub_Name"} = sub { local @ARGV = @_; return $main_sub->(); }; # Return 1 to make the script pass 'require' return 1; } } }; 1;


In reply to Main routines, unit tests, and sugar by stephen

Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":

  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?

    What's my password?
    Create A New User
    and all is quiet...

    How do I use this? | Other CB clients
    Other Users?
    Others cooling their heels in the Monastery: (10)
    As of 2017-05-23 21:13 GMT
    Find Nodes?
      Voting Booth?