Perl 6
The Cool Subset of Main
Perl
"Perl is a glue language
for the shell"
But we need more than glue!
And better things to glue...
So, we write our own command line programs
... Using Perl
We can parse args by hand
use strict;
my $thing = shift || 'world'; # I hope "0" is not an okay value :-)
my $required = shift or die "Nothing specified on the command line.\n";
my( $switch, $param );
$switch = shift;
if( $switch and $switch eq "-g" ) {
$param = shift || 'world';
} else {
$param = $switch || shift || 'meow';
$switch = undef if $switch;
}
print $switch ? 'Goodbye' : 'Hello', ", $param\n";
(then carry on...)
...Or use a module
use Getopt::Long;
my $data = "file.dat";
my $length = 24;
my $verbose;
GetOptions("length=i" => \$length, # numeric
"file=s" => \$data, # string
"verbose" => \$verbose) # flag
or die("Error in command line arguments\n");
(from perldoc GetOpt::Long, first result of "perl parse command line" on DDG)
That's in Perl 5.
It's different in Perl 6
... I'm not saying the grass is always greener
Perl 6
"Perl 6 is a highly capable, feature-rich programming language
made for the coming hundred years."
What do we need?
- Subsets
- Multi{method,sub}s
- MAIN
(as you might've noticed, I didn't manage to include a "Multimethod" pun in the title)
Subsets
Perl 6 is gradually typed
Which means you're not forced to put types on things, but if you do, they'll be enforced
(statically - at BEGIN time - if possible)
my Str $text = "I'm a string!"; # A Str
my Str @elems = "I'm", "a", "list", "of", "strings"; # Array of Str
my Int %hash{Str} = a => 1, b => 2; # hash of Str to Int
The basic types aren't enough!
Some example snippet
# .IO converts the Str to a IO::Path
# says True if "path" *e*xists
# (.IO converts a string to a IO::Path)
say "path".IO.e;
for "path1", "path2", "path3" {
say $_.IO.e;
# in Perl 6, we can also use a bare dot to mean "$_":
say .IO.e; # <- ".IO" acts on $_
}
(manually checks some condition)
Thankfully...
Perl 6 allows us to create subtypes
They're a stricter version of their parent, with a checked precondition
# Syntax:
# subset $New-name of Type where Condition
# (default Type is Any)
subset File of Str where .IO.e;
# a "File" is a "Str" that exists
# now, we can use smart-match:
say "myfile.p6" ~~ File; # True
say "this file doesn't exist" ~~ File; # False
# smart-matches against the range
subset SmallPosInt of Int where 0..10;
say 0 ~~ SmallPosInt; # True
say 10 ~~ SmallPosInt; # True
say 100000 ~~ SmallPosInt; # False
# same as doing this directly:
say 3 ~~ 0..10; # True
subset Comment of Str where /^'#'/;
say "abc" ~~ Comment; # False
say "# abc" ~~ Comment; # True
# read a file...
my @lines = 'file.c'.IO.lines;
# and extract the comments!
my @comments = @lines.grep(Comment);
Subs
Perl 6 has signatures
(which are a bit more capable than Perl 5.20's)
sub greet($name) {
# $name is interpolated inside double quotes
say "Hi $name!";
}
greet("people"); #=> Hi people!
# Perl 6 is a bit more permissive with identifiers:
sub greet-name($who-to-greet) {
# you can also use bracket to explicitly interpolate something
# (not needed here)
say "Hello {$who-to-greet}!";
}
greet-name("world"); #=> Hello world
# You can also add type constraints
sub welcome-back(Str $place) {
say "Welcome back $place!";
}
welcome-back("home"); #=> Welcome back home!
# welcome-back(3); # will fail
(If possible, the type check will fail at compile-time)
# You can ask the parameter to be coerced
# (here, from Int to Str)
sub say-count(Str(Int) $num) {
say $num ~~ Str;
}
# prints True:
# 5 has been converted to a Str
say-count(5);
# If the parentheses are empty, it'll convert any type to Str
# (by calling its .Str method)
sub as-string(Str() $n) {
$n eq "1 2 3 4 5";
}
as-string(1..5); # True as well
# You can also add by-name arguments
sub welcome(Str :$name) {
say "Named arguments are amazing, right, $name?";
}
# calling it:
welcome(name => "Perl People");
welcome(:name("Everybody"));
# this would fail:
#welcome("a string passed as a positional");
Multimethods
(or in our case, multi-subs)
How a basic
multi-subroutine looks like
multi sub print-me(Int $x) {
say "$x is an Int";
}
multi sub print-me(Str $x) {
say "$x is a Str";
}
# Literals are also allowed
multi sub strize(0) { say "Zero" }
# the "sub" is implicit:
multi strize(1) { say "One" }
# the parameter can be anonymous:
multi strize($) { say "(??)" }
You can use them with subsets
subset File of Str where .IO.e;
# use a type-constrained sub parameter
sub read-file(File $f) {
# Perl6 will check the file exists when you call read-file
say slurp $f;
}
subset Comment of Str where /^'#'/;
# No need to even have a sigil, the type is enough
multi sub is-comment(Comment) { True }
# (calling `is-comment` with a non-Str will still fail)
multi sub is-comment(Str) { False }
# equivalent to this form:
multi sub comments($x where /^'#'/, $y where /^'//') {
...
}
CLI
This is a valid Perl 6 program:
# (the star twigil is for variables like Perl5's `local`,
# which mean they are dynamically scoped)
my $name = @*ARGS[0];
# or
my ($name) = @*ARGS;
say "Hello, $name!";
But it's no improvement over what we had
(and it's pretty ugly/error-prone)
MAIN
Perl 6's built-in command-line parser
# if you have a sub named MAIN declared,
# Perl6 will call it for you (also generate a --help message).
sub MAIN($x, $y) {
say "the result is {$x + $y}";
}
Examples:
$ ./demo.p6 --help
Usage:
demo.p6 <x> <y>
$ ./demo.p6 3 4
the result is 7
$ ./demo.p6 "invalid arg"
Usage:
demo.p6 <x> <y>
Tying it all together
How can we get the most of these features, using them in conjunction?
- Types
- Multis
- Docs
- Named arguments
- (hidden) fallbacks
Type-checking MAIN
Examples:
sub MAIN(Int $x, Int $y) {
say "the result is {$x + $y}";
}
$ ./demo.p6 --help # still the same
Usage:
demo.p6 <x> <y>
$ ./demo.p6 3 4 # still the same
the result is 7
$ ./demo.p6 3 "some string here"
Usage:
demo.p6 <x> <y>
A Multi-MAIN
multi sub MAIN(Int $x, Int $y) {
say "the result is {$x + $y}";
}
# Use coercing to allow passing Int as Str
# for either param
multi sub MAIN(Str() $x, Str() $y) {
say "the result is {$x ~ $y}";
}
$ ./demo.p6
Usage:
demo.p6 <x> <y>
demo.p6 <x> <y>
$ ./demo.p6 3 4
the result is 7
$ ./demo.p6 "a" "b"
the result is ab
# this would fail without the coercing:
$ ./demo.p6 3 "foo"
the result is 3foo
We need a better --help
The usage message doesn't tell us any way to differentiate both versions.
Pod to the rescue!
Perl 5's POD's sister
Documenting a sub
#| Return a guaranteed-to-be-random value.
sub random-value {
return 4; # chosen by fair dice roll
}
say &random-value.WHY;
#=> prints "Return a guaranteed-to-be-random value."
Documenting MAIN
Examples:
#| Adds two numbers
sub MAIN(Int $x, Int $y) { say $x + $y; }
$ ./demo.p6 --help # still the same
Usage:
./demo.p6 <x> <y> -- Adds two numbers
$ ./demo.p6 3 4 # still the same
the result is 7
$ ./demo.p6 3 "some string here"
Usage:
./demo.p6 <x> <y> -- Adds two numbers
Getting more of our MULTIs
# again -- we can use literals!
multi MAIN('add', Int $x, Int $y) {
say $x + $y;
}
multi MAIN('div', Int $x, Int $y) {
say $x / $y;
}
multi MAIN('mult', Int $x, Int $y) {
say $x * $y;
}
$ ./calc.p6
Usage:
calc.p6 add <x> <y>
calc.p6 div <x> <y>
calc.p6 mult <x> <y>
$ ./calc.p6 add 3 4
7
$ ./calc.p6 div 3 4
0.75
$ ./calc.p6 camel 3 4
Usage:
calc.p6 add <x> <y>
calc.p6 div <x> <y>
calc.p6 mult <x> <y>
Exploiting named args
Examples:
sub MAIN(:$role = "lord") {
say "Well met, my $role";
}
$ ./named.p6 --help
Usage:
named.p6 [--role=<Any>]
$ ./named.p6
Well met, my lord
$ ./named.p6 --role=sir
Well met, my sir
Silly, pesky Any
Examples:
sub MAIN(Str :$role = "lord") {
say "Well met, my $role";
}
$ ./named.p6 --help
Usage:
named.p6 [--role=<Str>]
$ ./named.p6
Well met, my lord
$ ./named.p6 --role=sir
Well met, my sir
Togglable flags
Examples:
sub MAIN(Bool :$verbose) {
say "Turning on verbose mode..." if $verbose;
}
$ ./named.p6 --help
Usage:
named.p6 [--verbose]
$ ./named.p6
$ ./named.p6 --verbose
Turning on verbose mode...
Graceful error handling
subset File of Str where .IO.e;
subset NonFile of Str where !.IO.e;
multi sub MAIN(File $move-from, NonFile $move-to) {
rename $move-from, $move-to;
say "Moved!";
}
multi sub MAIN($, File $) {
say "The destination already exists";
}
multi sub MAIN(NonFile $, $) {
say "The source file doesn't exist";
}
Problem: --help is unhelpful
$ ./advent.p6
Usage:
advent.p6 <move-from> <move-to>
advent.p6 <Any> (File)
advent.p6 (NonFile) <Any>
The fallback candidates are documented, which we don't want.
It also shows an ugly "Any" for the arguments
(default type for a sub parameter)
Solution: hide them from USAGE
(--help's message)
subset File of Str where .IO.e;
subset NonFile of Str where !.IO.e;
#| Move a file
multi sub MAIN(File $move-from, NonFile $move-to) {
rename $move-from, $move-to;
say "Moved!";
}
multi sub MAIN($, File $) is hidden-from-USAGE {
say "The destination already exists";
}
multi sub MAIN(NonFile $, $) is hidden-from-USAGE {
say "The source file doesn't exist";
}
Resulting --help message
$ ./advent.p6
Usage:
advent.p6 <move-from> <move-to> -- Move a file
Only the "good" candidate is displayed, with its documentation.
The two others aren't in --help, but still work.
In conclusion...
Command-Line parsing grew up with Perl
With Perl 6, we gained the ability to declaratively create a CLI. Not because the language added a lot of special syntax or rules for that, but rather thanks to orthogonal features working together.
The Cool subset of MAIN
By Vendethiel
The Cool subset of MAIN
- 2,399