Note: In October 2019, Perl 6 was renamed to Raku. Whenever you come across Perl 6, replace it with Raku. Learn more about the path to Raku.
sub MAIN
In Perl 6, the parsing of command line arguments is done with the MAIN
subroutine which is a special subroutine that parses command line arguments
based on their signatures. As with other subroutines, you can have named and
positional parameters, optional (and required) parameters, multiple dispatch,
etc. with it.
With the definition of a MAIN
subroutine, a USAGE
subroutine is
automatically generated by the compiler. This subroutine can be modified to
return a customized usage message. All command line arguments are also available
in the special variable @*ARGS
, which can be mutated before being processed by
MAIN
.
Named and positional parameters
Named parameters
Let’s begin with a simple program (save as prog.p6
):
use v6;
sub MAIN(
Str :$name = 'John',
Str :$last-name = 'Doe',
) {
my $formatted-name = "$name.tc() $last-name.tc()";
say $formatted-name;
}
In this MAIN
sub, we have created two named parameters, $name
and
$last-name
with type constraints (Str
), by prepending :
to each variable in
the signature of the subroutine. These parameters also have default values which
is achieved by assigning a value to the parameter. In this case, we’ve set
$name
to the default value ‘John’ and $last-name
to ‘Doe’. If the command
line arguments match the MAIN
signature when prog.p6
is executed, then a
formatted full name will print out:
$ perl6 prog.p6
John Doe
$ perl6 prog.p6 --name='carl' --last-name='sagan'
Carl Sagan
$ perl6 prog.p6 --last-name='sagan' --name='carl'
Carl Sagan
As you can see, named parameters can be passed in whichever order you want.
If no MAIN
signature is matched, then we get a usage message:
$ p6 prog.p6 --name='Carl' --last-name='Sagan' --career='astronomer'
prog.p6 [--name=<Str>] [--last-name=<Str>]
Positional parameters
If we wanted to use positional parameters instead, we could redefine the subroutine’s signature to parse only positional parameters. Like the previous version, we’ll have default values for the parameters but these parameters are now positional and must be supplied in the order defined by the signature:
use v6;
sub MAIN(
Str $name = 'John', # No colon(:) in the variable
Str $last-name = 'Doe', # No colon(:) in the variable
) {
my $formatted-name = "$name.tc() $last-name.tc()";
say $formatted-name;
}
Executing the prog.p6
with a matching signature will print the following outputs:
$ perl6 prog.p6
John Doe
$ perl6 prog.p6 carl sagan
Carl Sagan
And without one, it gives us the following usage message:
$ perl6 prog.p6 carl sagan astronomer
prog.p6 [<name>] [<last-name>]
Multiple dispatch
We might prefer to use both named and positional parameters in our small
program. As we mentioned before, we can make use of multiple dispatch (several
subroutines with the same name but with different signatures) to declare
multiple MAIN
subroutines with their own signatures. To do this, each
candidate is declared with the multi
keyword instead of sub
:
use v6;
multi MAIN(
Str :$name = 'John',
Str :$last-name = 'Doe',
) {
my $formatted-name = "$name.tc() $last-name.tc()";
say $formatted-name;
}
multi MAIN(
Str $name = 'John',
Str $last-name = 'Doe',
) {
my $formatted-name = "$name.tc() $last-name.tc()";
say $formatted-name;
}
Both MAIN
subroutines look pretty much alike but they have different
signatures which describe the expected command line arguments.
If we execute prog.p6
with command line arguments that match any of the MAIN
signatures, we’ll get our formatted full name:
$ p6 prog.p6 --name='ada' --last-name='lovelace'
Ada Lovelace
$ p6 prog.p6 marcus aurelius
Marcus Aurelius
And without a matching signature, we’ll get a useful usage message detailing the
possible signatures for our MAIN
subroutine:
$ p6 prog.p6 --name='Ada' --last-name='Lovelace' --title='Ms'
Usage:
prog.p6 [--name=<Str>] [--last-name=<Str>]
prog.p6 [<name>] [<last-name>]
Combining named and positional parameters
Defining different signatures to tackle different command line arguments, named
and positional parameters in our case, is fine. But, what if you wanted to mix
named and positional parameters in a single MAIN
signature? This can be done quite
easily, although the positional parameters must be defined before the named ones.
Let’s update the latest iteration of our simple program prog.p6
by adding a
positional parameter to the first multi
subroutine:
use v6;
multi MAIN(
Str $title = 'Mr', # Our positional parameter defined before named ones
Str :$name = 'John',
Str :$last-name = 'Doe',
) {
my $formatted-name = "$title.tc() $name.tc() $last-name.tc()";
say $formatted-name;
}
multi MAIN(
Str $title,
Str $name = 'John',
Str $last-name = 'Doe',
) {
my $formatted-name = "$title.tc() $name.tc() $last-name.tc()";
say $formatted-name;
}
Optional and required parameters
Named parameters are optional by default. Nonetheless, they can be marked as
required by appending the respective lexical variable with !
. For example,
MAIN( :$first, :$second, :$operator ){ ... }
wouldn’t print a usage message if
called without some command line arguments but MAIN( :$first!, :$second!, :$operator! ){ ... }
would do given that the parameters are now required and
the caller must pass the necessary arguments.
On the other hand, positional parameters are required by default but can be
marked as optional by appending the respective lexical variable with ?
. For
example, MAIN( $first, $second, $operator ){ ... }
would print a usage message
if called without command line arguments but MAIN( $first?, $second?, $operator? ){ ... }
wouldn’t due to the parameters being now optional.
Positional parameters can also be defined as optional by setting default values
like how we did with $name
and $last-name
in
multi MAIN( $title, $name = 'John', $last-name = 'Doe' ) { ... }
.
Aliases or alternate named parameters
Named parameters and their aliases are provided by the use of the colon-pair
syntax (:
). The presence of the colon :
will decide whether we are creating
a new named parameter or not.
Let’s modify the first multi
in prog.p6
to include some aliases:
use v6;
multi MAIN(
Str $title = 'Mr',
Str :$name = 'John',
Str :last-name($surname) = 'Doe',
Bool :p(:$print),
) {
my $formatted-name = "$title.tc() $name.tc() $surname.tc()";
if $print {
say $formatted-name;
}
}
This MAIN
is defining two kind of aliases:
:last-name($surname)
only aliases the content passed to the command line parameter--last-name
to the variable$surname
(notice the lack of:
). This means$surname
will be just the name of the aliased variable which does not create a new named parameter:
$ p6 prog.p6 --name='alan' --last-name='turing' -p
Alan Turing
$ p6 prog.p6 --name='alan' --surname='turing'
Usage:
pos-named.p6 [--name=<Str>] [--last-name=<Str>] [-p|--print] [<title>]
:$print
will not only be the name of the aliased variable, but also a new named parameter, alongside:p
:
$ p6 prog.p6 --name='alan' --last-name='turing'
$ p6 prog.p6 --name='alan' --last-name='turing' -p
Alan Turing
$ p6 prog.p6 --name='alan' --last-name='turing' -print
Alan Turing
As you may have noticed, the flag -p
(or -print
) must be now specified if we
want to print the person’s formatted full name. This is because the type Bool
makes
$print
a binary flag which is False
if absent. If called, then the flag is
True
, making the execution of our simple if $print { ... }
statement
possible.
Using aliases is an easy way to create long form and short form option names for
parameters. We could further modify the first multi
in prog.p6
in order to
provide a short form option name for --name
and --last-name
:
use v6;
multi MAIN(
Str $title = 'Mr',
Str :n(:$name) = 'John',
Str :l(:last-name($surname)) = 'Doe',
Bool :p(:$print),
) {
my $formatted-name = "$title.tc() $name.tc() $surname.tc()";
if $print {
say $formatted-name;
}
}
By executing prog.p6
with different form options, we get:
p6 prog.p6 --name='alan' --last-name='turing' -print
Mr. Alan Turing
p6 prog.p6 -n='grace' -l='hopper' -p 'Ms'
Ms. Grace Hopper
And without a matching signature, we get our usage message:
p6 prog.p6 -n='alan' -l='turing' -p --career='mathematician'
Usage:
prog.p6 [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>]
prog.p6 [<title>] [<name>] [<last-name>]
Sub USAGE
Without a matching signature, the latest iteration of our small program prog.p6
would print the following usage message:
Usage:
prog.p6 [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>]
prog.p6 <title> [<name>] [<last-name>]
which is due to the USAGE
subroutine being called automatically when no
matching signature is supplied to the MAIN
subroutine. If no such subroutine is
found, the compiler will output a default generated usage message which means
that we can define it to provide a more detailed (if that’s we want!) usage message.
This is prog.p6
with a modified USAGE
sub:
use v6;
multi MAIN(
Str $title = 'Mr',
Str :n(:$name) = 'John',
Str :l(:last-name($surname)) = 'Doe',
Bool :p(:$print),
) {
my $formatted-name = "$title.tc() $name.tc() $surname.tc()";
if $print {
say $formatted-name;
}
}
multi MAIN(
Str $title = 'Mr',
Str $name = 'John',
Str $last-name = 'Doe',
) {
my $formatted-name = "$title.tc() $name.tc() $last-name.tc()";
say $formatted-name;
}
sub USAGE() {
print Q:c:to/END/;
Usage:
{$*PROGRAM-NAME} [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>]
{$*PROGRAM-NAME} [<title>] [<name>] [<last-name>]
optional arguments:
-h, --help show this help message and exit
-n=PERSON_NAME, --name=PERSON_NAME
specify person's name
-l=PERSON_LAST_NAME, --last-name=PERSON_LAST_NAME
specify person's last name
-p , --print print person's full name
<title> specify person's title ('Mr' by default)
Examples:
{$*PROGRAM-NAME} --name='richard' --last-name='feynman' -p
{$*PROGRAM-NAME} --name='sophie' --last-name='germain' -p 'Ms'
{$*PROGRAM-NAME} 'leonhard' 'euler'
END
}
Notice the mention of the -h
(and --help
) flags in the
usage message which we didn’t need to explicitly define because they
are automatically generated. If we now execute prog.p6
with the --help
(or -h
)
flag or provide no matching signature, we get the new usage message:
Usage:
prog.p6 [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>]
prog.p6 [<title>] [<name>] [<last-name>]
optional arguments:
-h, --help show this help message and exit
-n=PERSON_NAME, --name=PERSON_NAME
specify person's name
-l=PERSON_LAST_NAME, --last-name=PERSON_LAST_NAME
specify person's last name
-p , --print print person's full name
<title> specify person's title ('Mr' by default)
Examples:
prog.p6 --name='richard' --last-name='feynman' -p
prog.p6 --name='sophie' --last-name='germain' -p 'Ms'
prog.p6 'leonhard' 'euler'
Conclusion
This is certainly just a superficial look at the MAIN
and USAGE
subroutines.
As it is in Perl 6, there are always more things than meets the eye. For example if
you want named arguments to be placed anywhere in the command line (even after
positional parameters), you could modify the hash %*SUB-MAIN-OPTS
to allow
this behavior. If you want to go into more details, I’ve provided
some useful links right below.
Also see:
- More about subroutines, blocks, signatures, return values, MAIN sub, etc.
- Interacting with the command line with MAIN subs
- Perl 6 Signatures - Jonathan Worthington
- Parsing Command Line Arguments in Perl 6
- Parsing command line arguments in Perl 6
- How do I parse and validate command line arguments in Perl 6?