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

  • 1,072
Loading comments...

More from Vendethiel