NAME
Perl6::Controls - Add Perl 6 control structures as Perl 5 keywords
VERSION
This document describes Perl6::Controls version 0.000007
SYNOPSIS
use Perl6::Controls;
try {
CATCH { warn 'No more animals :-(' }
loop {
my @animals;
repeat while (!@animals) {
say 'Enter animals: ';
scalar readline // die
==> split /\s+/,
==> grep {exists $group_of{$_} }
==> @animals;
}
for (%group_of{@animals}) -> $animal, $group {
FIRST { say "\n(" }
say " '$animal' => '$group',";
LAST { say ")\n" }
}
}
}
DESCRIPTION
This module steals some of the most useful control structures provided by Perl 6 and retrofits them to Perl 5, via the extensible keyword mechanism.
INTERFACE
loop {...}
The loop
loop is simply an infinite loop:
loop {
say 'Are we there yet?';
sleep 60;
}
Note that this module does not implement the extended Perl 6 loop (INIT; COND; INCR) {...}
syntax, as that is already covered by Perl 5's builtin for
.
for (LIST) -> $VAR1, $VAR2, $ETC {...}
The module adds an additional syntax for for
loops, which allows one or more (lexical) iterator variables to be specified in the Perl 6 way: after the list.
That is, instead of:
for my $pet ('cat', 'dog', 'fish') {
say "Can I have a $pet?";
}
you can write:
for ('cat', 'dog', 'fish') -> $pet {
say "Can I have a $pet?";
}
The real advantage of the Perl 6 syntax is that you can specify two or more iterator variables for the same loop, in which case the loop will iterate its list N-at-a-time. For example:
for (%pet_prices) -> $pet, $price {
say "A $pet costs $price";
}
Note that, unlike in Perl 6, the list to be iterated still requires parentheses around it.
Under Perl v5.22 and later, you can specify the variables after the arrow with a leading reference, in which case the corresponding values are aliased to that variable (see "Assigning to References" in perlref).
Note that the relevant experimental features must be activated to use this.
For example:
use experimental qw< refaliasing declared_refs>;
for (%hash_of_arrays) -> $key, \@array {
# Print the contents of each hash entry...
say "$key has $_" foreach @array;
# Append an element to each nested array...
push @array, 42;s
}
while (COND) -> $VAR {...}
The module also adds a similar new syntax for while
loops, allowing them to test their condition and assign it to a lexically scoped variable using the "arrow" syntax.
That is, instead of:
while (my $input = readline) {
process( $input );
}
You can write:
while (readline) -> $input {
process( $input );
}
Note that, unlike the modified for
syntax, this modified while
only allows a single variable after the arrow.
repeat while (COND) {...}
repeat until (COND) {...}
repeat {...} while COND
repeat {...} until COND
The Perl 5 do...while
and do...until
constructs are not proper loops (they're quantified do
statements). This means, for example, that you can't use next
, last
, or redo
to control them.
The Perl 6 repeat
constructs are genuine "iterate-then-test" loops, and also allow the condition to be specified either after or before the block.
Therefore, instead of:
do {
print 'Next value: ';
$value = get_data() // last; # Oops!
}
while ($value !~ /^\d+$/);
or equivalently:
do {
print 'Next value: ';
$value = get_data() // last; # Oops!
}
until ($value =~ /^\d+$/);
you can write any of the following:
repeat {
print 'Next value: ';
$value = get_data() // last; # Works as expected
}
while ($value !~ /^\d+$/);
repeat {
print 'Next value: ';
$value = get_data() // last; # Works as expected
}
until ($value =~ /^\d+$/);
repeat while ($value !~ /^\d+$/) {
print 'Next value: ';
$value = get_data() // last; # Works as expected
}
repeat until ($value =~ /^\d+$/) {
print 'Next value: ';
$value = get_data() // last; # Works as expected
}
FIRST {...}
NEXT {...}
LAST {...}
These special blocks can only be placed in a loop block, and will execute at different points in each execution of the surrounding loop.
The FIRST
block is executed only on the first iteration of the surrounding loop. The NEXT
block iterates at the end of every iteration of the surrounding loop. The LAST
block executes only after the final iteration of the surrounding loop.
For example, instead of:
if (@list) {
print '(';
for my $elem (@list) {
print $elem;
print ',';
}
print ')';
}
you could write:
for my $elem (@list) {
FIRST { print '('; }
print $elem;
NEXT { print ','; }
LAST { print ')'; }
}
or (because the order and position in which the special blocks are declared does not affect when they are called):
for my $elem (@list) {
FIRST { print '('; }
NEXT { print ','; }
LAST { print ')'; }
print $elem;
}
LEAVE {...}
A LEAVE
block is executed whenever control exits the surrounding block, either by falling out the bottom, or via a return
, or next
, or last
, or redo
, or goto
, or die
.
try { ... CATCH {...} ... }
The try
block is more or less equivalent to a Perl 5 eval
, except that it is a proper block, so it doesn't return a value, but it also doesn't require a semicolon after it. The try
intercepts and neutralizes any exception called within its block.
The CATCH
block specifies an alternative response when the surrounding try
intercepts an exception. It may be specified with a parameter, to which the intercepted exception is then assigned. The CATCH
block also acts like a given
, so the exception is always aliased to $_
, and the block may contain when
and default
statements as well.
For example:
try {
$data = acquire_data(); # May throw exception
CATCH ($err) {
when (/bad data/) { warn 'Ignoring bad data' }
when (/no data/) { exit -1; }
default { die $err; } # Rethrow unknown exception
}
}
Note that the CATCH
block is always optional within a try
, and may be placed anywhere within the try
block.
feed EXPR ==> EXPR ==> ... ==> EXPR ;
This simulates the Perl 6 feed operator (==>
).
Due to limitations in the Perl 5 extension mechanism, the keyword feed
is required at the start of the feed sequence.
Each expression can be a Perl list operator or n-ary function or a simple variable or variable declaration. Any simple variable appearing in the sequence is assigned to (in list context). Anything else has the result of the preceding expression in the pipeline appended to its trailing argument list.
For example:
feed readline() ==> my @input ==> sort ==> join ':', ==> $result;
is equivalent to:
($result) = join ':',
sort
(my @input) =
readline;
Note that the join
step must end in a comma, so that appending the sorted input to it will create a syntactically correct expression.
DIAGNOSTICS
Can't specify CATCH block outside a try
-
The syntax for Perl 6
try
blocks requires theCATCH
block to be placed inside thetry
block (so that it has access to any lexical variables declared in thetry
).You declared a
CATCH
block outside of anytry
, which means that block could never be invoked. Either add atry
block around the source of potential errors that you are trying to catch, or remove theCATCH
. Can't specify two CATCH blocks inside a single try
-
try
blocks have a single slot for theirCATCH
callback, and it's already full, because you already specified anotherCATCH
block earlier in the sametry
block.Consolidate the code in the two blocks into a single block. (Remember, it doesn't matter where in the
try
you place theCATCH
.)
CONFIGURATION AND ENVIRONMENT
Perl6::Controls requires no configuration files or environment variables.
DEPENDENCIES
Requires Perl 5.14 and the Keyword::Declare module.
INCOMPATIBILITIES
Due to a problem with regex parsing in Perl v5.20, code using this module with compile absurdly slowly under that version of the interpreter. There is no such problem with any other version of Perl from v5.14 onwards.
BUGS AND LIMITATIONS
Because the blocks of FIRST
, LAST
, NEXT
, and CATCH
are converted to subroutines, any call to return
within one of these blocks will terminate the block, but not the surrounding subroutine.
Unlike in Perl 6, the FIRST
phaser does not execute before the first iteration, but rather only during the first iteration. This means that if you want it to execute at the start of the first iteration, you need to place it at the start of the iterated block.
The underlying keyword rewriting mechanism (or perhaps the Keyword::Simple API) seems to have a bug when it comes to keywords that are also postfix quantifiers. This means that code using this module cannot also use postfix for
or while
. It can, however, still use postfix foreach
or until
.
No bugs have been reported.
Please report any bugs or feature requests to bug-perl6-controls@rt.cpan.org
, or through the web interface at http://rt.cpan.org.
AUTHOR
Damian Conway <DCONWAY@CPAN.org>
LICENCE AND COPYRIGHT
Copyright (c) 2017, Damian Conway <DCONWAY@CPAN.org>
. All rights reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.
DISCLAIMER OF WARRANTY
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.